X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/0494b8d9b9bb03ab6c22b34dae81261e3cd7e3e6..7a654f8d43fdb43d78b63d90528bed6e86b608cc:/src/data/proxy/Server.js?ds=sidebyside diff --git a/src/data/proxy/Server.js b/src/data/proxy/Server.js new file mode 100644 index 00000000..d674f7a4 --- /dev/null +++ b/src/data/proxy/Server.js @@ -0,0 +1,460 @@ +/** + * @author Ed Spencer + * @class Ext.data.proxy.Server + * @extends Ext.data.proxy.Proxy + * + *

ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, + * and would not usually be used directly.

+ * + *

ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been + * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now an + * alias of AjaxProxy).

+ */ +Ext.define('Ext.data.proxy.Server', { + extend: 'Ext.data.proxy.Proxy', + alias : 'proxy.server', + alternateClassName: 'Ext.data.ServerProxy', + uses : ['Ext.data.Request'], + + /** + * @cfg {String} url The URL from which to request the data object. + */ + + /** + * @cfg {Object/String/Ext.data.reader.Reader} reader The Ext.data.reader.Reader to use to decode the server's response. This can + * either be a Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml'). + */ + + /** + * @cfg {Object/String/Ext.data.writer.Writer} writer The Ext.data.writer.Writer to use to encode any request sent to the server. + * This can either be a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml'). + */ + + /** + * @cfg {String} pageParam The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to + * undefined if you don't want to send a page parameter + */ + pageParam: 'page', + + /** + * @cfg {String} startParam The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this + * to undefined if you don't want to send a start parameter + */ + startParam: 'start', + + /** + * @cfg {String} limitParam The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this + * to undefined if you don't want to send a limit parameter + */ + limitParam: 'limit', + + /** + * @cfg {String} groupParam The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this + * to undefined if you don't want to send a group parameter + */ + groupParam: 'group', + + /** + * @cfg {String} sortParam The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this + * to undefined if you don't want to send a sort parameter + */ + sortParam: 'sort', + + /** + * @cfg {String} filterParam The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set + * this to undefined if you don't want to send a filter parameter + */ + filterParam: 'filter', + + /** + * @cfg {String} directionParam The name of the direction parameter to send in a request. This is only used when simpleSortMode is set to true. + * Defaults to 'dir'. + */ + directionParam: 'dir', + + /** + * @cfg {Boolean} simpleSortMode Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a remote sort is requested. + * The directionParam and sortParam will be sent with the property name and either 'ASC' or 'DESC' + */ + simpleSortMode: false, + + /** + * @cfg {Boolean} noCache (optional) Defaults to true. Disable caching by adding a unique parameter + * name to the request. + */ + noCache : true, + + /** + * @cfg {String} cacheString The name of the cache param added to the url when using noCache (defaults to "_dc") + */ + cacheString: "_dc", + + /** + * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds. + */ + timeout : 30000, + + /** + * @cfg {Object} api + * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy". + * Defaults to:

+api: {
+    read    : undefined,
+    create  : undefined,
+    update  : undefined,
+    destroy : undefined
+}
+     * 
+ *

The url is built based upon the action being executed [load|create|save|destroy] + * using the commensurate {@link #api} property, or if undefined default to the + * configured {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.


+ *

For example:

+ *

+api: {
+    load :    '/controller/load',
+    create :  '/controller/new',
+    save :    '/controller/update',
+    destroy : '/controller/destroy_action'
+}
+     * 
+ *

If the specific URL for a given CRUD action is undefined, the CRUD action request + * will be directed to the configured {@link Ext.data.proxy.Server#url url}.

+ */ + + /** + * @ignore + */ + constructor: function(config) { + var me = this; + + config = config || {}; + this.addEvents( + /** + * @event exception + * Fires when the server returns an exception + * @param {Ext.data.proxy.Proxy} this + * @param {Object} response The response from the AJAX request + * @param {Ext.data.Operation} operation The operation that triggered request + */ + 'exception' + ); + me.callParent([config]); + + /** + * @cfg {Object} extraParams Extra parameters that will be included on every request. Individual requests with params + * of the same name will override these params when they are in conflict. + */ + me.extraParams = config.extraParams || {}; + + me.api = config.api || {}; + + //backwards compatibility, will be deprecated in 5.0 + me.nocache = me.noCache; + }, + + //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case + create: function() { + return this.doRequest.apply(this, arguments); + }, + + read: function() { + return this.doRequest.apply(this, arguments); + }, + + update: function() { + return this.doRequest.apply(this, arguments); + }, + + destroy: function() { + return this.doRequest.apply(this, arguments); + }, + + /** + * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store} + * that this Proxy is attached to. + * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute + * @return {Ext.data.Request} The request object + */ + buildRequest: function(operation) { + var params = Ext.applyIf(operation.params || {}, this.extraParams || {}), + request; + + //copy any sorters, filters etc into the params so they can be sent over the wire + params = Ext.applyIf(params, this.getParams(params, operation)); + + if (operation.id && !params.id) { + params.id = operation.id; + } + + request = Ext.create('Ext.data.Request', { + params : params, + action : operation.action, + records : operation.records, + operation: operation, + url : operation.url + }); + + request.url = this.buildUrl(request); + + /* + * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the + * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing + */ + operation.request = request; + + return request; + }, + + /** + * + */ + processResponse: function(success, operation, request, response, callback, scope){ + var me = this, + reader, + result, + records, + length, + mc, + record, + i; + + if (success === true) { + reader = me.getReader(); + result = reader.read(me.extractResponseData(response)); + records = result.records; + length = records.length; + + if (result.success !== false) { + mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();}); + mc.addAll(operation.records); + for (i = 0; i < length; i++) { + record = mc.get(records[i].getId()); + + if (record) { + record.beginEdit(); + record.set(record.data); + record.endEdit(true); + } + } + + //see comment in buildRequest for why we include the response object here + Ext.apply(operation, { + response: response, + resultSet: result + }); + + operation.setCompleted(); + operation.setSuccessful(); + } else { + operation.setException(result.message); + me.fireEvent('exception', this, response, operation); + } + } else { + me.setException(operation, response); + me.fireEvent('exception', this, response, operation); + } + + //this callback is the one that was passed to the 'read' or 'write' function above + if (typeof callback == 'function') { + callback.call(scope || me, operation); + } + + me.afterRequest(request, success); + }, + + /** + * Sets up an exception on the operation + * @private + * @param {Ext.data.Operation} operation The operation + * @param {Object} response The response + */ + setException: function(operation, response){ + operation.setException({ + status: response.status, + statusText: response.statusText + }); + }, + + /** + * Template method to allow subclasses to specify how to get the response for the reader. + * @private + * @param {Object} response The server response + * @return {Mixed} The response data to be used by the reader + */ + extractResponseData: function(response){ + return response; + }, + + /** + * Encode any values being sent to the server. Can be overridden in subclasses. + * @private + * @param {Array} An array of sorters/filters. + * @return {Mixed} The encoded value + */ + applyEncoding: function(value){ + return Ext.encode(value); + }, + + /** + * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default, + * this simply JSON-encodes the sorter data + * @param {Array} sorters The array of {@link Ext.util.Sorter Sorter} objects + * @return {String} The encoded sorters + */ + encodeSorters: function(sorters) { + var min = [], + length = sorters.length, + i = 0; + + for (; i < length; i++) { + min[i] = { + property : sorters[i].property, + direction: sorters[i].direction + }; + } + return this.applyEncoding(min); + + }, + + /** + * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default, + * this simply JSON-encodes the filter data + * @param {Array} sorters The array of {@link Ext.util.Filter Filter} objects + * @return {String} The encoded filters + */ + encodeFilters: function(filters) { + var min = [], + length = filters.length, + i = 0; + + for (; i < length; i++) { + min[i] = { + property: filters[i].property, + value : filters[i].value + }; + } + return this.applyEncoding(min); + }, + + /** + * @private + * Copy any sorters, filters etc into the params so they can be sent over the wire + */ + getParams: function(params, operation) { + params = params || {}; + + var me = this, + isDef = Ext.isDefined, + groupers = operation.groupers, + sorters = operation.sorters, + filters = operation.filters, + page = operation.page, + start = operation.start, + limit = operation.limit, + + simpleSortMode = me.simpleSortMode, + + pageParam = me.pageParam, + startParam = me.startParam, + limitParam = me.limitParam, + groupParam = me.groupParam, + sortParam = me.sortParam, + filterParam = me.filterParam, + directionParam = me.directionParam; + + if (pageParam && isDef(page)) { + params[pageParam] = page; + } + + if (startParam && isDef(start)) { + params[startParam] = start; + } + + if (limitParam && isDef(limit)) { + params[limitParam] = limit; + } + + if (groupParam && groupers && groupers.length > 0) { + // Grouper is a subclass of sorter, so we can just use the sorter method + params[groupParam] = me.encodeSorters(groupers); + } + + if (sortParam && sorters && sorters.length > 0) { + if (simpleSortMode) { + params[sortParam] = sorters[0].property; + params[directionParam] = sorters[0].direction; + } else { + params[sortParam] = me.encodeSorters(sorters); + } + + } + + if (filterParam && filters && filters.length > 0) { + params[filterParam] = me.encodeFilters(filters); + } + + return params; + }, + + /** + * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will + * add the cache-buster param to the end of the url. Subclasses may need to perform additional modifications + * to the url. + * @param {Ext.data.Request} request The request object + * @return {String} The url + */ + buildUrl: function(request) { + var me = this, + url = me.getUrl(request); + + // + if (!url) { + Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url."); + } + // + + if (me.noCache) { + url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now())); + } + + return url; + }, + + /** + * Get the url for the request taking into account the order of priority, + * - The request + * - The api + * - The url + * @private + * @param {Ext.data.Request} request The request + * @return {String} The url + */ + getUrl: function(request){ + return request.url || this.api[request.action] || this.url; + }, + + /** + * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all pass + * through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link Ext.data.proxy.JsonP} + * and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as each of the methods that delegate to it. + * @param {Ext.data.Operation} operation The Ext.data.Operation object + * @param {Function} callback The callback function to call when the Operation has completed + * @param {Object} scope The scope in which to execute the callback + */ + doRequest: function(operation, callback, scope) { + // + Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details"); + // + }, + + /** + * Optional callback function which can be used to clean up after a request has been completed. + * @param {Ext.data.Request} request The Request object + * @param {Boolean} success True if the request was successful + */ + afterRequest: Ext.emptyFn, + + onDestroy: function() { + Ext.destroy(this.reader, this.writer); + } +});