com.smartclient.debug.public.sc.client.application.RestDataSource.js Maven / Gradle / Ivy
Show all versions of smartgwt Show documentation
/*
* Isomorphic SmartClient
* Version SC_SNAPSHOT-2011-08-08 (2011-08-08)
* Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
* "SmartClient" is a trademark of Isomorphic Software, Inc.
*
* [email protected]
*
* http://smartclient.com/license
*/
// Visit http://www.smartclient.com for more information on Isomorphic SmartClient
//> @class RestDataSource
// The RestDataSource implements the 4 core DataSource operations using a simple protocol of
// XML or JSON requests and responses sent over HTTP, which can be easily fulfilled by any HTTP
// server technology.
//
// RestDataSource is named for the
// +externalLink{http://www.google.com/search?hl=en&q=REST+HTTP,REST} (REpresentational State
// Transfer) pattern, which in brief says that simple messages passed over HTTP is a sufficient
// protocol for many web applications, without the need for further protocols such as WSDL or
// SOAP.
//
// 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 SmartClient
// to backends which do not already support a similar, pre-existing request and response
// format and where the SmartClient Java Server cannot be used.
//
// If you have a pre-existing REST or WSDL service which is difficult to change, consider
// adapting SmartClient to the existing service instead, by starting with a normal
// +link{DataSource} and using the
// +link{group:clientDataIntegration,client-side data integration} facilities to create a
// mapping between SmartClient's +link{DSRequest} and +link{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 SmartClient Server in a Java technology
// stack, or using +link{group:wsdlBinding,WSDL-based binding} with .NET or other WSDL-capable
// technologies.
//
// The request and response formats used by the RestDataSource allow for many of the available
// features of SmartClient's databinding system to be used, including data paging, searching &
// sorting, +link{dsRequest.oldValues,long transactions},
// +link{ResultSet,automatic cache sync} and +link{group:relogin,relogin}. However advanced
// features such as +link{group:upload,uploading / binary fields},
// +link{RPCManager.startQueue,queuing} and transaction chaining,
// +link{listGrid.exportData,export} and all +link{group:iscServer,server-based features} aren't
// available with RestDataSource and need to be re-implemented as needed.
//
// 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>
// ... 75 total records ...
// </data>
// </response>
//
// The <status> element indicates whether the fetch operation was successful
// (see +link{group: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{dsResponse.startRow,startRow}, +link{dsResponse.endRow,endRow} and
// +link{dsResponse.totalRows,totalRows} properties of the +link{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{dataSource.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{RPCResponse.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{RPCManager.handleError} to be
// invoked, which by default will show the provided error message as an alert using
// +link{classMethod: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"},
// ... 75 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{RPCResponse.STATUS_VALIDATION_ERROR} [-4
], and errors 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{OperationBinding.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{RPCRequest.params}.
// If +link{RestDataSource.sendMetaData} is true, the DSRequest meta
// data properties will also be present as parameters, prefixed with
// +link{RestDataSource.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{DSRequest} that is meaningful to the
// server had been passed to +link{dataSource.xmlSerialize()} or +link{JSON.encode()}
// respectively.
//
// An example of an XML message might look like this:
//
// <request>
// <data>
// <countryDS>
// <countryCode>US</countryCode>
// <countryName>Edited Value</countryName>
// <capital>Edited Value</capital>
// <continent>Edited Value</continent>
// </countryDS>
// </data>
// <dataSource>countryDS</dataSource>
// <operationType>update</operationType>
// </request>
//
// The +link{restDataSource.operationBindings,default OperationBindings} for a RestDataSource
// specify dataProtocol as "getParams" for the fetch operation, and "postParams" for update,
// add and remove operations.
//
// Hierarchical (Tree) data:
//
// To create a hierarchical DataSource, in the DataSource's 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{DataSourceField.foreignKey} and the
// +link{DataSourceField.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.
// Tree data is typically displayed using a dataBound +link{class:TreeGrid} component. TreeGrids
// automatically create a +link{class: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.
// 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.
// 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{group:treeDataBinding, Tree Databinding}.
//
// @treeLocation Client Reference/Data Binding
// @visibility external
// @example restEditSave
//<
isc.defineClass("RestDataSource", "DataSource");
isc.RestDataSource.addProperties({
serverType:"generic",
//> @attr restDataSource.dataFormat (string : "xml" : IR)
// Expected format for server responses. RestDataSources handle "json"
and
// "xml"
format responses by default. See class overview documentation for
// examples of responses in each format.
// @visibility external
//<
dataFormat:"xml",
//> @attr restDataSource.xmlRecordXPath (string : "/response/data/*" : IR)
// recordXPath
mapping to the data node of XML returned by the server.
// Applies if this.dataFormat is set to "xml"
.
// The default value will pick up data from a response structured as follows:
//
// <response>
// <status>0</status>
// <data>
// <record>
// <field1>value</field1>
// <field2>value</field2>
// </record>
// <record>
// <field1>value</field1>
// <field2>value</field2>
// </record>
// </data>
// </response>
//
// @visibility external
//<
xmlRecordXPath:"/response/data/*",
//> @attr restDataSource.jsonRecordXPath (string : "/response/data" : IR)
// recordXPath
mapping to the data node of json returned by the server.
// Applies if this.dataFormat is set to "json"
// The default value will pick up data from a response structured as follows:
//
// {response:
// {status:0,
// data:[
// {field1:"value", field2:"value"},
// {field1:"value", field2:"value"}
// ]
// }
//
// @visibility external
//<
jsonRecordXPath:"/response/data",
//> @attr restDataSource.recordXPath (string : null : IRW)
// For RestDataSources, by default, either the +link{RestDataSource.xmlRecordXPath} or
// +link{RestDataSource.jsonRecordXPath} is used based on the +link{dataFormat}
// setting.
//
// Note that you can also apply record xpath binding via
// +link{operationBinding.recordXPath}.
//
// @visibility external
//<
//> @attr restDataSource.prettyPrintJSON (boolean : true : IR)
// When using dataFormat:"json" and dataProtocol:"postMessage" should we use the
// +link{JSONEncoder.prettyPrint} feature to enable indented, highly readable JSON messages.
//
// True by default because the bandwidth involved is generally negligible and the benefits for
// troubleshooting are key.
//
// @visibility external
//<
prettyPrintJSON: true,
// Override init to pick up these paths
// Set default encapsulation for JSON messages
init : function () {
this.recordXPath = this.recordXPath ||
(this.dataFormat == "xml" ? this.xmlRecordXPath : this.jsonRecordXPath);
this.jsonPrefix = this.jsonPrefix || "//isc_JSONResponseStart-->";
this.jsonSuffix = this.jsonSuffix || "//isc_JSONResponseEnd";
return this.Super("init", arguments);
},
//> @attr RestDataSource.operationBindings (Array of OperationBinding : [...] : IR)
// RestDataSource OperationBindings set to specify default dataProtocol per operationType.
// Default databindings are:
//
// operationBindings : [
// {operationType:"fetch", dataProtocol:"getParams"},
// {operationType:"add", dataProtocol:"postParams"},
// {operationType:"remove", dataProtocol:"postParams"},
// {operationType:"update", dataProtocol:"postParams"}
// ],
//
// If you are integrating with a +link{RestDataSource,REST} server that requires the more
// obscure +link{rpcRequest.httpMethod}s of "PUT", "DELETE" or "HEAD", you can specify these
// httpMethod settings via +link{operationBinding.requestProperties}. dataProtocol settings
// that mention "GET" or "POST" are compatible with these additional HTTP methods as well.
// Typical +link{dataSource.operationBindings,operationBindings} for a REST server that uses
// "PUT" and "DELETE" are as follows:
//
// operationBindings:[
// {operationType:"fetch", dataProtocol:"getParams"},
// {operationType:"add", dataProtocol:"postParams"},
// {operationType:"remove", dataProtocol:"getParams", requestProperties:{httpMethod:"DELETE"}},
// {operationType:"update", dataProtocol:"postParams", requestProperties:{httpMethod:"PUT"}}
// ],
//
//
// @visibility external
//<
operationBindings:[
{operationType:"fetch", dataProtocol:"getParams"},
{operationType:"add", dataProtocol:"postParams"},
{operationType:"remove", dataProtocol:"postParams"},
{operationType:"update", dataProtocol:"postParams"}
],
//> @attr RestDataSource.dataURL (string : null : IR)
// Default URL to contact to fulfill all DSRequests.
// RestDataSources also allow per-operationType dataURLs to be set via
//
// - +link{RestDataSource.fetchDataURL}
// - +link{RestDataSource.addDataURL}
// - +link{RestDataSource.updateDataURL}
// - +link{RestDataSource.removeDataURL}
//
// @visibility external
//<
//> @attr RestDataSource.fetchDataURL (string : null : IR)
// Custom dataURL for fetch type operations
// @visibility external
//<
//> @attr RestDataSource.updateDataURL (string : null : IR)
// Custom dataURL for update type operations
// @visibility external
//<
//> @attr RestDataSource.addDataURL (string : null : IR)
// Custom dataURL for add type operations
// @visibility external
//<
//> @attr RestDataSource.removeDataURL (string : null : IR)
// Custom dataURL for remove type operations
// @visibility external
//<
//> @attr RestDataSource.sendMetaData (boolean : true : IR)
// Should operation meta data be included when assembling parameters to send
// to the server? If true, meta data parameters will be prefixed with the
// +link{RestDataSource.metaDataPrefix}.
// Applies to operations where OperationBinding.dataProtocol is set to
// "getParams"
or "postParams"
only.
// @visibility external
//<
sendMetaData:true,
//> @attr RestDataSource.metaDataPrefix (string : "_" :IR)
// If +link{RestDataSource.sendMetaData} is true, this attribute is used to specify
// the prefix to apply to 'meta data' properties when assembling parameters to send to the
// server. Applies to operations where OperationBinding.dataProtocol is set to
// "getParams"
or "postParams"
only.
// @visibility external
//<
metaDataPrefix:"_",
//> @attr RestDataSource.sendClientContext (boolean : null : IRW)
// If true the +link{dsRequest.clientContext} will be sent to the server as a parameter
// along with the request.
// @visibility internal
//<
// only has an effect for "postMessage" data protocol
// sendClientContext:null,
// getDataURL()
// overridden to respect fetchDataURL et al.
getDataURL : function (dsRequest) {
var type = dsRequest.operationType;
if (type == "fetch" && this.fetchDataURL != null)
return this.fetchDataURL;
if (type == "update" && this.updateDataURL != null)
return this.updateDataURL;
if (type == "add" && this.addDataURL != null)
return this.addDataURL;
if (type == "remove" && this.removeDataURL != null)
return this.removeDataURL;
return this.Super("getDataURL", arguments);
},
// Override getDataProtocol - treat postXML dataProtocol specification as postMessage.
getDataProtocol : function (dsRequest) {
var protocol = this.Super("getDataProtocol", arguments);
if (protocol == "postXML") protocol = "postMessage";
return protocol;
},
//> @method RestDataSource.transformRequest()
// RestDataSource overrides transformRequest and handles serializing the request in the
// appropriate format (determined by the specified
// +link{operationBinding.dataProtocol,dataProtocol}), including the submitted
// +link{DSRequest.data,data} as well as the meta data parameters, which may include -
// +link{DSRequest.dataSource,dataSource},
// +link{DSRequest.operationType,operationType}, +link{DSRequest.operationId,operationId};
// +link{DSRequest.startRow,startRow} and +link{DSRequest.endRow,endRow} (for fetches);
// +link{DSRequest.sortBy,sortBy} and +link{DSRequest.textMatchStyle,textMatchStyle}
// (for fetches);
// +link{DSRequest.oldValues,oldValues} (for update and remove operations);
// and possibly +link{DSRequest.componentId,componentId}.
//
// If you override this method in order to add additional data to the DSRequest, you must
// call +link{Class.Super,Super()} or you will remove the functionality provided by
// RestDataSource. For example:
//
// transformRequest : function (dsRequest) {
// // modify dsRequest.data here, for example, add fixed criteria
// dsRequest.data.userId = myApplication.getCurrentUserId();
//
// return this.Super("transformRequest", arguments);
// }
//
//
// See +link{class:RestDataSource,RestDataSource overview} for a description of the
// standard formatting applied to requests.
//
// @visibility external
//<
transformRequest : function (dsRequest) {
var protocol = this.getDataProtocol(dsRequest);
// "postMessage": Post data as XML serialized message
if (protocol == "postMessage") {
// Set parameter specifying request/response data format
if (dsRequest.params == null) {
dsRequest.params = {};
}
dsRequest.params["isc_dataFormat"] = this.dataFormat;
var params = {
dataSource:this.getID()
};
// omit metadata fields if they're not set on the dsRequest
if (dsRequest.operationType != null) params.operationType = dsRequest.operationType;
if (dsRequest.operationId != null) params.operationId = dsRequest.operationId;
if (dsRequest.startRow != null) params.startRow = dsRequest.startRow;
if (dsRequest.endRow != null) params.endRow = dsRequest.endRow;
if (dsRequest.sortBy != null) params.sortBy = dsRequest.sortBy;
if (dsRequest.textMatchStyle != null) params.textMatchStyle = dsRequest.textMatchStyle;
if (this.sendClientContext) params.clientContext = dsRequest.clientContext;
// send the componentId if present
if (dsRequest.componentId) params.componentId = dsRequest.componentId;
var ds = isc.DataSource.create({
fields:[
{name:"data", multiple:true, type:this.getID()},
{name:"oldValues", type:this.getID()}
]
});
if (this.autoConvertRelativeDates == true) {
// convert any relative dates in criteria into absolute dates so the server
// doesn't need to know how to handle relative dates
if (this.logIsInfoEnabled("relativeDates")) {
this.logInfo("Calling convertRelativeDates from getServiceInputs "+
"- data is\n\n"+isc.echoFull(dsRequest.data));
}
var transformedData = this.convertRelativeDates(dsRequest.data);
if (this.logIsInfoEnabled("relativeDates")) {
this.logInfo("Called convertRelativeDates from getServiceInputs "+
"- data is\n\n"+isc.echoFull(transformedData));
}
dsRequest.data = transformedData;
}
params.data = dsRequest.data;
params.oldValues = dsRequest.oldValues;
if (!dsRequest.contentType) {
dsRequest.contentType = (this.dataFormat == "json" ?
"application/json" : "text/xml");
}
if (this.dataFormat == "json") {
if (params.data != null) params.data = this.serializeFields(params.data);
if (params.oldValues != null) params.oldValues = this.serializeFields(params.oldValues);
var settings = {prettyPrint: this.prettyPrintJSON};
return isc.JSON.encode(params, settings);
} else {
return ds.xmlSerialize(params, null, null, "request");
}
// "getParams" / "postParams": HTTP Parameters format
} else {
if (protocol != "getParams" && protocol != "postParams") {
this.logWarn("RestDataSource operation:"+ dsRequest.operationID + ", of type "
+ dsRequest.operationType + " has dataProtocol specified as '"
+ protocol + "'. Supported protocols are 'postParams', 'getParams' "
+ "and 'postMessage' only. Defaulting to 'getParams'.");
dsRequest.dataProtocol = 'getParams';
}
// All fields passed in as 'data' will be available directly as parameters
// Also include any explicit parameters present on the dsRequest
var params = isc.addProperties({}, dsRequest.data, dsRequest.params);
// Attach meta data parameters to the transaction
if (this.sendMetaData) {
if (!this.parameterNameMap) {
var map = {};
map[this.metaDataPrefix + "operationType"] = "operationType";
map[this.metaDataPrefix + "operationId"] = "operationId";
map[this.metaDataPrefix + "startRow"] = "startRow";
map[this.metaDataPrefix + "endRow"] = "endRow";
map[this.metaDataPrefix + "sortBy"] = "sortBy";
map[this.metaDataPrefix + "textMatchStyle"] = "textMatchStyle";
map[this.metaDataPrefix + "oldValues"] = "oldValues";
map[this.metaDataPrefix + "componentId"] = "componentId";
this.parameterNameMap = map;
}
// Meta data will be available as parameters with the metaDataPrefix applied
for (var parameterName in this.parameterNameMap) {
var value = dsRequest[this.parameterNameMap[parameterName]];
if (value != null) params[parameterName] = value;
}
params[this.metaDataPrefix + "dataSource"] = this.getID();
params["isc_metaDataPrefix"] = this.metaDataPrefix;
}
// Set parameter specifying response data format
params["isc_dataFormat"] = this.dataFormat;
return params;
}
},
// getUpdatedData() overridden to use request.originalData if dataProtocol is "postMessage"
// as in this case request.data will be a serialized string of data rather than a javascript
// object.
getUpdatedData : function (dsRequest, dsResponse, useDataFromRequest) {
var data = dsResponse ? dsResponse.data : null;
if (useDataFromRequest &&
(!data || isc.isAn.emptyString(data) ||
(isc.isA.Array(data) && data.length == 0)) &&
dsResponse.status == 0 &&
this.getDataProtocol(dsRequest) == "postMessage")
{
this.logInfo("dsResponse for successful operation of type " + dsRequest.operationType +
" did not return updated record[s]. Using submitted request data to update"
+ " ResultSet cache.", "ResultSet");
// look at the originalData object - stored before transformRequest was called
var updateData = {},
requestData = dsRequest.originalData;
if (requestData && isc.isAn.Object(requestData)) {
// if operationType is an update, request data will be sparse so need to combine
// with oldValues
if (dsRequest.operationType == "update") {
updateData = isc.addProperties({}, dsRequest.oldValues);
// Assertion - we only update one record at a time, so if submitted data is an array
// it will contain one entry that matches the 'oldValues'
if (isc.isAn.Array(requestData)) {
updateData = isc.addProperties(updateData, requestData[0]);
} else {
updateData = isc.addProperties(updateData, requestData);
}
updateData = [updateData];
// for add or delete old values are irrelevant
} else {
if (!isc.isAn.Array(requestData)) requestData = [requestData];
updateData = [];
for (var i = 0; i < requestData.length; i++) {
updateData[i] = isc.addProperties({}, requestData[i]);
}
}
//>DEBUG
if (this.logIsDebugEnabled("ResultSet")) {
this.logDebug("Submitted data to be integrated into the cache:"
+ this.echoAll(updateData), "ResultSet");
}
// @method RestDataSource.transformResponse()
// RestDataSource implements transformResponse in order to extract data and meta-data
// properties from server responses, as described in the
// +link{class:RestDataSource,RestDataSource overview}.
//
// You can override transformResponse()
in order to further modify the
// response, but if you do so, call +link{class.Super,Super()} as shown below or you will
// wipe out the built-in response processing behavior of RestDataSource.
//
// transformResponse : function (dsResponse, dsRequest, data) {
// var dsResponse = this.Super("transformResponse", arguments);
// // ... do something to dsResponse ...
// return dsResponse;
// }
//
// @param dsResponse (DSResponse) default DSResponse derived from the response data
// @param dsRequest (DSRequest) DSRequest object that initiated this request
// @param data (XMLDocument or JSON) XML document or JSON objects returned by the web
// service
// @return (DSResponse) response derived
//
// @visibility external
//<
transformResponse : function (dsResponse, dsRequest, data) {
if (dsResponse.status < 0 || !data) return dsResponse;
if (this.dataFormat == "json") {
var rawResponse = data.response || {};
dsResponse.status = this.getValidStatus(rawResponse.status);
// if the status is a validation error, convert the errors from XML
if (dsResponse.status == isc.DSResponse.STATUS_VALIDATION_ERROR) {
var errors = rawResponse.errors;
// Handle being returned an array of errors (per row) or a single error object
// for the modified row.
if (isc.isAn.Array(errors)) {
if (errors.length > 1) {
this.logWarn("server returned an array of errors - ignoring all but the first one");
}
errors = errors[0];
}
dsResponse.errors = errors;
// handle being passed a failure status with 'data' being an error string to display
} else if (dsResponse.status < 0) {
dsResponse.data = rawResponse.data;
}
if (rawResponse.totalRows != null) dsResponse.totalRows = rawResponse.totalRows;
if (rawResponse.startRow != null) dsResponse.startRow = rawResponse.startRow;
if (rawResponse.endRow != null) dsResponse.endRow = rawResponse.endRow;
} else {
dsResponse.status = this.getValidStatus(data.selectString("//status"));
// if the status is a validation error, convert the errors from XML
if (dsResponse.status == isc.DSResponse.STATUS_VALIDATION_ERROR) {
var errors = data.selectNodes("//errors");
errors = isc.xml.toJS(errors);
if (errors.length > 1) {
this.logWarn("server returned an array of errors - ignoring all but the first one");
}
errors = errors[0];
dsResponse.errors = errors;
// handle being passed a raw response where 'data' is an error string to display
} else if (dsResponse.status < 0) {
dsResponse.data = data.selectString("//data");
}
var totalRows = data.selectNumber("//totalRows");
if (totalRows != null) dsResponse.totalRows = totalRows;
var startRow = data.selectNumber("//startRow");
if (startRow != null) dsResponse.startRow = startRow;
var endRow = data.selectNumber("//endRow");
if (endRow != null) dsResponse.endRow = endRow;
}
return dsResponse;
}
});