A RestDataSource is used just like a normal DataSource. RestDataSources are pre-configured, using the general-purpose databinding facilities of DataSources, to expect a particular format for responses and to send requests in a specific format. These request and response formats represent Isomorphic's recommended best practices for binding Smart GWT to backends which do not already support a similar, pre-existing request and response format and where the Smart GWT Java Server cannot be used.
If you have a pre-existing REST or WSDL service which is difficult to change, consider adapting Smart GWT to the existing service instead, by starting with a normal {@link com.smartgwt.client.data.DataSource} and using the {@link com.smartgwt.client.docs.ClientDataIntegration client-side data integration} facilities to create amapping between Smart GWT's {@link com.smartgwt.client.data.DSRequest} and {@link com.smartgwt.client.data.DSResponse}objects and the message formats of your existing services.
RestDataSource is typically used with PHP, Ruby, Python, Perl or custom server technologies, and represents an alternative to installing the Smart GWT Server in a Java technology stack, or using {@link com.smartgwt.client.docs.WsdlBinding WSDL-based binding} with .NET or other WSDL-capabletechnologies. Note that Smart GWT Server also provides built-in support for the REST protocol via its RESTHandler servlet; this is primarily to allow non-Smart GWT clients to make use of DataSource operations. If you particularly wished to do so, you could use RestDataSource to make a Smart GWT app talk to the Smart GWT Server using REST rather than the proprietary wire format normally used when communicating with Smart GWT Server (this is how we are able to write automated tests for the RESTHandler servlet). However, doing this provides no benefit, imposes a number of inconveniences, and makes a handful of server-based features less useful ( {@link com.smartgwt.client.docs.serverds.DataSourceField#viewRequiresAuthentication field-level declarative security}, for example), so we strongly recommend that you do not do this; it is only mentioned here for completeness while we are discussing REST.
The request and response formats used by the RestDataSource allow for many of the available features of Smart GWT's databinding system to be used, including data paging, searching & sorting, {@link com.smartgwt.client.data.DSRequest#getOldValues long transactions}, {@link com.smartgwt.client.data.ResultSet automatic cache sync}, {@link com.smartgwt.client.docs.Relogin relogin} and {@link com.smartgwt.client.rpc.RPCManager#startQueue queuing}. However, advanced features such as {@link com.smartgwt.client.docs.Upload uploading / binary fields} and {@link com.smartgwt.client.widgets.grid.ListGrid#exportData export} aren't available with RestDataSource and need to be re-implemented as needed. Most, though not all, {@link com.smartgwt.client.docs.IscServer server-based features}are still available when using RestDataSource, as long as you are also using the RESTHandler servlet that is part of Smart GWT Server. However, as noted above, this approach is not recommended; if you are using Isomorphic technology both client- and server-side, it makes more sense to use the proprietary wire format.
Examples
XML formatted responses:
RestDataSource expects a response like the following in response to a "fetch" request:
<response> <status>0</status> <startRow>0</startRow> <endRow>76</endRow> <totalRows>546</totalRows> <data> <record> <field1>value</field1> <field2>value</field2> </record> <record> <field1>value</field1> <field2>value</field2> </record> ... 76 total records ... </data> </response>The <status> element indicates whether the fetch operation was successful (see {@link com.smartgwt.client.docs.StatusCodes}).
The <data> element contains a list of record nodes, each of which represents a record returned by the server. The optional <startRow>, <endRow> and <totalRows> elements are needed only if data paging is in use, and populate the {@link com.smartgwt.client.data.DSResponse#getStartRow startRow}, {@link com.smartgwt.client.data.DSResponse#getEndRow endRow} and{@link com.smartgwt.client.data.DSResponse#getTotalRows totalRows} properties of the {@link com.smartgwt.client.data.DSResponse}.
Note: for a more compact format, simple field values may be specified on record nodes directly as attributes - in this case a record element might be structured like this:
<record field1="value" field2="value" />
Note that a RestDataSource will bypass browser caching of all responses by default. See {@link com.smartgwt.client.data.DataSource#getPreventHTTPCaching preventHTTPCaching}.
Successful "add" or "update" request responses are similar in format - in this case the data element would be expected to contain a single record object containing the details of the record, as saved on the server.
The response from a "remove" operation would again include status and data elements, but in this case, only the primary key field value(s) of the removed record would be expected to be present under the data element.
If a validation failure occurred on the server, the response would have status set to {@link com.smartgwt.client.rpc.RPCResponse#STATUS_VALIDATION_ERROR STATUS_VALIDATION_ERROR}[-4
], and any validation errors could be included as per-field sub-elements of an "errors" element. For a validation error, the response is not expected to contain any <data> element.
A response showing a validation error might look like this:
<response> <status>-4</status> <errors> <field1> <errorMessage>A validation error occurred for this field</errorMessage> </field1> </errors> </response>
An unrecoverable error, such as an unexpected server failure, can be flagged by setting <status> to -1 and setting <data> to an error message. In this case the <errors> element is not used (it's specific to validation errors). An unrecoverable error causes all response processing to be skipped and {@link com.smartgwt.client.rpc.RPCManager#handleError RPCManager.handleError} to beinvoked, which by default will show the provided error message as an alert using {@link com.smartgwt.client.util.isc#warn isc.warn}.
JSON formatted responses:
JSON format responses are expected to contain the same data / meta-data as XMLresponses, encapsulated a simple object with a "response"
attribute.
The response to a "fetch" request would therefore have this format:
{ response:{ status:0, startRow:0, endRow:76, totalRows:546, data:[ {field1:"value", field2:"value"}, {field1:"value", field2:"value"}, ... 76 total records ... ] } }The structure successful for "add", "update" and "remove" responses would be similar, though the data array would be expected to contain only a single object, representing the values as saved. This allows the server to return values such as an auto-generated sequence primaryKey, a last modified timestamp, or similar server-generated field values.
For a remove, only the value for the primaryKey field[s] would be required.
For a validation error, the status
attribute would be set to {@link com.smartgwt.client.rpc.RPCResponse#STATUS_VALIDATION_ERROR STATUS_VALIDATION_ERROR} [-4
], anderrors would be specified in the errors
attribute of the response. For example:
{ response: { status:-4, errors: { field1:{errorMessage:"A validation error on field1"}, field2:{errorMessage:"A validation error on field2"} } } }An array of errors may also be returned for a single field, like this:
{ response: { status:-4, errors: { field1:[ {errorMessage:"First error on field1"}, {errorMessage:"Second error on field1"} ] } } }
As with the XML format above, an unrecoverable error is indicated by setting the status
attribute to -1 and the data
property to the error message.
Server inbound data formats
The format of data sent to the server is determined by the {@link com.smartgwt.client.data.OperationBinding#getDataProtocol dataProtocol}specified for the operation. Request data is sent as parameters if the format is specified as "getParams"
or "postParams"
.
In this case, the parameters sent to the server will consist of the DSRequest's data, and any parameters explicitly specified on the DSRequest object (as {@link com.smartgwt.client.rpc.RPCRequest#getParams params}.
If {@link com.smartgwt.client.data.RestDataSource#getSendMetaData sendMetaData} is true, the DSRequest meta data properties will also be present as parameters, prefixed with {@link com.smartgwt.client.data.RestDataSource#getMetaDataPrefix metaDataPrefix}.
Example URL constructed with the metaDataPrefix set to "_"
(the default):
[dataURL]?field1=value1&_operationType=fetch&_startRow=0&_endRow=50&_sortBy=-field2&_dataSource=dsName
In this case the server would be able to separate the request's data from the meta data via the "_"
prefix.
If data is sent to the server via the "postMessage"
dataProtocol, the data will be serialized as an XML or JSON message according to the dataFormat
setting. Both XML and JSON messages will contain request metadata such as startRow and endRow, and will appear exactly as though the subset of the {@link com.smartgwt.client.data.DSRequest} that is meaningful to theserver had been passed to {@link com.smartgwt.client.data.DataSource#xmlSerialize DataSource.xmlSerialize} or {@link com.smartgwt.client.util.JSON#encode JSON.encode}respectively.
An example of an XML message might look like this:
<request> <data> <countryCode>US</countryCode> <countryName>Edited Value</countryName> <capital>Edited Value</capital> <continent>Edited Value</continent> </data> <dataSource>countryDS</dataSource> <operationType>update</operationType> </request>An example of an XML message for a fetch operation passing simple criteria:
<request> <data> <continent>North America</continent> </data> <dataSource>countryDS</dataSource> <operationType>fetch</operationType> <startRow>0</startRow> <endRow>75</endRow> <componentId>worldGrid</componentId> <textMatchStyle>exact</textMatchStyle> </request>And an example of an XML message for a fetch operation passing AdvancedCriteria:
<request> <data> <_constructor>AdvancedCriteria</_constructor> <operator>or</operator> <criteria> <criterion> <fieldName>continent</fieldName> <operator>equals</continent> <value>North America</value> </criterion> <criterion> <operator>and</operator> <criteria> <criterion> <fieldName>continent</fieldName> <operator>equals</operator> <value>Europe</value> </criterion> <criterion> <fieldName>population</fieldName> <operator>greaterThan</operator> <value>50000000</value> </criterion> </criteria> </criterion> </criteria> </data> <dataSource>countryDS</dataSource> <operationType>fetch</operationType> <startRow>0</startRow> <endRow>75</endRow> <componentId>worldGrid</componentId> </request>JSON messages are just the plain JSON form of the structures shown in the above XML examples. To show the last of the three XML examples in JSON form:
{ data: { _constructor: "AdvancedCriteria", operator: "or", criteria: [ { fieldName: "continent", operator: "equals", value: "North America }, { operator: "and", criteria: [ { fieldName: "continent", operator: "equals", value: "Europe" }, { fieldName: "population", operator: "greaterThan", value: 50000000 } ] } ] } dataSource: "countryDS", operationType: "fetch", startRow: 0, endRow: 75, componentId: "worldGrid" }The {@link com.smartgwt.client.data.RestDataSource#getOperationBindings default OperationBindings} for a RestDataSourcespecify dataProtocol as "getParams" for the fetch operation, and "postParams" for update, add and remove operations.
Date, time and datetime values
Date, time and datetime values must be communicated using XML Schema format, as in the following examples:
<dateField>2007-04-22</dateField>
<timeField>11:07:13</timeField>
<dateTimeField>2007-04-22T11:07:13</dateTimeField>
And the equivalent in JSON: Both RestDataSource on the client-side and the RESTHandler servlet on the server side automatically handle encoding and decoding temporal values using these formats. Fields of type "date" and "time" are considered to hold logical date and time values, as discussed in the {@link com.smartgwt.client.docs.DateFormatAndStorage date and time handling article}, and are not affected by timezones. Fields of type "datetime" will be converted to UTC on the client side by RestDataSource, and will be sent back down to the client as UTC by the server-side RESTHandler. We recommend that your own REST client and/or server code do the same thing (ie, transmit all datetime values in both directions as UTC). RestDataSource queuing support RestDataSource supports {@link com.smartgwt.client.rpc.RPCManager#startQueue queuing} of DSRequests. This allows you to send multiple requests to the server in a single HTTP turnaround, thus minimizing network traffic and allowing the server to treat multiple requests as a single transaction, if the server is able to do so (in Power Edition and above, the Smart GWT Server transparently supports grouping multiple REST requests in a queue into a single database transaction when using one of the built-in DataSource types). If you want to use queuing with RestDataSource, you must use the "postMessage" dataProtocol with either XML or JSON dataFormat. Message format is similar to the non-queued examples shown earlier: it is simply extended to cope with the idea of multiple DSRequests encapsulated in the message. An example of the XML message sent from RestDataSource to the server for two update requests combined into a queue, using XML dataFormat: The update queue example given above would expect a response like this (in XML): To create a hierarchical DataSource, in the DataSource's Tree data is typically displayed using a dataBound {@link com.smartgwt.client.widgets.tree.TreeGrid} component.TreeGrids automatically create a ResultTree data object, which requests data directly from the DataSource. ResultTrees load data on demand, only requesting currently visible (open) nodes from the server. This is handled by including a specified value for the parent id field in the request criteria. Specifically, Add and Update operations may change the structure of the tree by returning a new parent id field value for the modified node. Depending on how your data is stored you may need to include special back-end logic to handle this. Also, if a user deletes a folder within a databound tree, any children of that folder will also be dropped from the tree, and can be removed from the back-end data storage. Note: For a general overview of binding components to Tree structured data, see {@link com.smartgwt.client.docs.TreeDataBinding Tree Databinding}.
dateField: "2007-04-22"
timeField: "11:07:13"
dateTimeField: "2007-04-22T11:07:13"
<transaction> <operations> <request> <data> <pk>1</pk> <countryName>Edited Value</countryName> <capital>Edited Value</capital> <continent>Edited Value</continent> </data> <dataSource>countryDS</dataSource> <operationType>update</operationType> </request> <request> <data> <pk>2</pk> <capital>Edited Value</capital> <population>123456</population> </data> <dataSource>countryDS</dataSource> <operationType>update</operationType> </request> </operations> <transaction>
And the same message in JSON format: { transaction: { operations: [{ dataSource:"countryDS", operationType:"update", data: { pk: 1 countryName: "Edited Value", capital: "Edited Value", continent: "Edited Value" } }, { dataSource:"countryDS", operationType:"update", data: { pk: 2, capital: "Edited Value", popuilation: 123456 } }] } }
RestDataSource expects the response to a queue of requests to be a queue of responses in the same order as the original requests. Again, the message format is very similar to the unqueued REST format, it just has an outer container construct. Note also that the individual DSResponses in a queued response have an extra property, queueStatus
. This allows each individual response to determine whether the queue as a whole succeeded. For example, if the first update succeeded but the second failed validation, the first response would have a status
of 0, but a queueStatus
of -1, while the second response would have both properties set to -1. <responses> <response> <status>0</status> <queueStatus>0</queueStatus> <data> <record> <countryName>Edited Value</countryName> <gdp>1700.0</gdp> <continent>Edited Value</continent> <capital>Edited Value</capital> <pk>1</pk> </record> </data> </response> <response> <status>0</status> <queueStatus>0</queueStatus> <data> <record> <countryName>United States</countryName> <gdp>7247700.0</gdp> <continent>North America</continent> <independence>1776-07-04</independence> <capital>Washington DC</capital> <pk>2</pk> <population>123456</population> </record> </data> </response> </responses>
And in JSON: [ { response: { queueStatus: 0, status: 0, data: [{ countryName: "Edited Value", gdp: 1700.0, continent":"Edited Value", capital: "Edited Value", pk: 1 }] } }, { response: { queueStatus: 0, status: 0, data: [{ countryName:"United States", gdp: 7247700.0, continent":"North America, independence: Date.parseServerDate(1776,6,4), capital: "Washington DC", pk: 2, population: 123456 }] } } ]
Hierarchical (Tree) data: fields
array, a field must be specified as the parent id field - the field which will contain a pointer to the id of each node's parent. This can be achieved by setting the {@link com.smartgwt.client.data.DataSourceField#getForeignKey foreignKey} and the {@link com.smartgwt.client.data.DataSourceField#getRootValue rootValue} attributes on the field definition. For example: RestDataSource.create({ ID:"supplyItem", fields : [ {name:"itemId", type:"sequence", primaryKey:true}, {name:"parentId", type:"integer", foreignKey:"supplyItem.itemId", rootValue:0}, ... ] });
Tree Data is then treated on the server as a flat list of records linked by parent id.
To implement a standard load-on-demand tree RestDataSource back end, you should therefore simply return the set of nodes that match the criteria passed in. For example, if your DataSource was defined as the "supplyItem" code snippet above, a fetch request for all children of a node with itemId
set to 12
would have "parentId"
set to 12
in the request criteria. A valid response would then contain all the records that matched this criteria. For example: <response> <status>0</status> <data> <record> <itemId>15</itemId> <parentId>12</parentId> </record> <record> <itemId>16</itemId> <parentId>12</parentId> </record> </data> </response>
The structure of responses for Add, Update and Delete type requests will be the same regardless of whether the data is hierarchical. However you should be aware that the underlying data storage may need to be managed slightly differently in some cases.
|
|
|
|
|
|