/** * @author Ed Spencer * * 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). * @private */ 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 {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 * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true. */ 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 * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds). */ timeout : 30000, /** * @cfg {Object} api * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to: * * api: { * create : undefined, * read : undefined, * update : undefined, * destroy : undefined * } * * The url is built based upon the action being executed [create|read|update|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: { * create : '/controller/new', * read : '/controller/load', * update : '/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}. */ constructor: function(config) { var me = this; config = config || {}; /** * @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 */ 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 = Ext.apply({}, config.api || me.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); }, /** * Sets a value in the underlying {@link #extraParams}. * @param {String} name The key for the new value * @param {Object} value The value */ setExtraParam: function(name, value) { this.extraParams[name] = value; }, /** * 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 me = this, params = Ext.applyIf(operation.params || {}, me.extraParams || {}), request; //copy any sorters, filters etc into the params so they can be sent over the wire params = Ext.applyIf(params, me.getParams(operation)); if (operation.id && !params.id) { params.id = operation.id; } request = new Ext.data.Request({ params : params, action : operation.action, records : operation.records, operation: operation, url : operation.url, // this is needed by JsonSimlet in order to properly construct responses for // requests from this proxy proxy: me }); request.url = me.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; }, // Should this be documented as protected method? processResponse: function(success, operation, request, response, callback, scope) { var me = this, reader, result; if (success === true) { reader = me.getReader(); // Apply defaults to incoming data only for read operations. // For create and update, there will already be a client-side record // to match with which will contain any defaulted in values. reader.applyDefaults = operation.action === 'read'; result = reader.read(me.extractResponseData(response)); if (result.success !== false) { //see comment in buildRequest for why we include the response object here Ext.apply(operation, { response: response, resultSet: result }); operation.commitRecords(result.records); 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. * @template * @private * @param {Object} response The server response * @return {Object} 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 {Object} 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 {Ext.util.Sorter[]} 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 {Ext.util.Filter[]} filters 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(operation) { var me = this, params = {}, 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 * @method */ afterRequest: Ext.emptyFn, onDestroy: function() { Ext.destroy(this.reader, this.writer); } });