Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / data / proxy / Server.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Ed Spencer
17  *
18  * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
19  * would not usually be used directly.
20  *
21  * ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
22  * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now
23  * an alias of AjaxProxy).
24  * @private
25  */
26 Ext.define('Ext.data.proxy.Server', {
27     extend: 'Ext.data.proxy.Proxy',
28     alias : 'proxy.server',
29     alternateClassName: 'Ext.data.ServerProxy',
30     uses  : ['Ext.data.Request'],
31
32     /**
33      * @cfg {String} url
34      * The URL from which to request the data object.
35      */
36
37     /**
38      * @cfg {String} pageParam
39      * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to undefined if you don't
40      * want to send a page parameter.
41      */
42     pageParam: 'page',
43
44     /**
45      * @cfg {String} startParam
46      * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to undefined if you don't
47      * want to send a start parameter.
48      */
49     startParam: 'start',
50
51     /**
52      * @cfg {String} limitParam
53      * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to undefined if you don't
54      * want to send a limit parameter.
55      */
56     limitParam: 'limit',
57
58     /**
59      * @cfg {String} groupParam
60      * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to undefined if you don't
61      * want to send a group parameter.
62      */
63     groupParam: 'group',
64
65     /**
66      * @cfg {String} sortParam
67      * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to undefined if you don't
68      * want to send a sort parameter.
69      */
70     sortParam: 'sort',
71
72     /**
73      * @cfg {String} filterParam
74      * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to undefined if you don't
75      * want to send a filter parameter.
76      */
77     filterParam: 'filter',
78
79     /**
80      * @cfg {String} directionParam
81      * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to
82      * true.** Defaults to 'dir'.
83      */
84     directionParam: 'dir',
85
86     /**
87      * @cfg {Boolean} simpleSortMode
88      * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a
89      * remote sort is requested. The directionParam and sortParam will be sent with the property name and either 'ASC'
90      * or 'DESC'.
91      */
92     simpleSortMode: false,
93
94     /**
95      * @cfg {Boolean} noCache
96      * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.
97      */
98     noCache : true,
99
100     /**
101      * @cfg {String} cacheString
102      * The name of the cache param added to the url when using noCache. Defaults to "_dc".
103      */
104     cacheString: "_dc",
105
106     /**
107      * @cfg {Number} timeout
108      * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).
109      */
110     timeout : 30000,
111
112     /**
113      * @cfg {Object} api
114      * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
115      *
116      *     api: {
117      *         create  : undefined,
118      *         read    : undefined,
119      *         update  : undefined,
120      *         destroy : undefined
121      *     }
122      *
123      * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
124      * {@link #api} property, or if undefined default to the configured
125      * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
126      *
127      * For example:
128      *
129      *     api: {
130      *         create  : '/controller/new',
131      *         read    : '/controller/load',
132      *         update  : '/controller/update',
133      *         destroy : '/controller/destroy_action'
134      *     }
135      *
136      * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
137      * configured {@link Ext.data.proxy.Server#url url}.
138      */
139
140     constructor: function(config) {
141         var me = this;
142
143         config = config || {};
144         this.addEvents(
145             /**
146              * @event exception
147              * Fires when the server returns an exception
148              * @param {Ext.data.proxy.Proxy} this
149              * @param {Object} response The response from the AJAX request
150              * @param {Ext.data.Operation} operation The operation that triggered request
151              */
152             'exception'
153         );
154         me.callParent([config]);
155
156         /**
157          * @cfg {Object} extraParams
158          * Extra parameters that will be included on every request. Individual requests with params of the same name
159          * will override these params when they are in conflict.
160          */
161         me.extraParams = config.extraParams || {};
162
163         me.api = config.api || {};
164
165         //backwards compatibility, will be deprecated in 5.0
166         me.nocache = me.noCache;
167     },
168
169     //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
170     create: function() {
171         return this.doRequest.apply(this, arguments);
172     },
173
174     read: function() {
175         return this.doRequest.apply(this, arguments);
176     },
177
178     update: function() {
179         return this.doRequest.apply(this, arguments);
180     },
181
182     destroy: function() {
183         return this.doRequest.apply(this, arguments);
184     },
185
186     /**
187      * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
188      * that this Proxy is attached to.
189      * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
190      * @return {Ext.data.Request} The request object
191      */
192     buildRequest: function(operation) {
193         var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
194             request;
195
196         //copy any sorters, filters etc into the params so they can be sent over the wire
197         params = Ext.applyIf(params, this.getParams(operation));
198
199         if (operation.id && !params.id) {
200             params.id = operation.id;
201         }
202
203         request = Ext.create('Ext.data.Request', {
204             params   : params,
205             action   : operation.action,
206             records  : operation.records,
207             operation: operation,
208             url      : operation.url
209         });
210
211         request.url = this.buildUrl(request);
212
213         /*
214          * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
215          * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
216          */
217         operation.request = request;
218
219         return request;
220     },
221
222     // Should this be documented as protected method?
223     processResponse: function(success, operation, request, response, callback, scope){
224         var me = this,
225             reader,
226             result;
227
228         if (success === true) {
229             reader = me.getReader();
230             result = reader.read(me.extractResponseData(response));
231
232             if (result.success !== false) {
233                 //see comment in buildRequest for why we include the response object here
234                 Ext.apply(operation, {
235                     response: response,
236                     resultSet: result
237                 });
238
239                 operation.commitRecords(result.records);
240                 operation.setCompleted();
241                 operation.setSuccessful();
242             } else {
243                 operation.setException(result.message);
244                 me.fireEvent('exception', this, response, operation);
245             }
246         } else {
247             me.setException(operation, response);
248             me.fireEvent('exception', this, response, operation);
249         }
250
251         //this callback is the one that was passed to the 'read' or 'write' function above
252         if (typeof callback == 'function') {
253             callback.call(scope || me, operation);
254         }
255
256         me.afterRequest(request, success);
257     },
258
259     /**
260      * Sets up an exception on the operation
261      * @private
262      * @param {Ext.data.Operation} operation The operation
263      * @param {Object} response The response
264      */
265     setException: function(operation, response){
266         operation.setException({
267             status: response.status,
268             statusText: response.statusText
269         });
270     },
271
272     /**
273      * Template method to allow subclasses to specify how to get the response for the reader.
274      * @template
275      * @private
276      * @param {Object} response The server response
277      * @return {Object} The response data to be used by the reader
278      */
279     extractResponseData: function(response){
280         return response;
281     },
282
283     /**
284      * Encode any values being sent to the server. Can be overridden in subclasses.
285      * @private
286      * @param {Array} An array of sorters/filters.
287      * @return {Object} The encoded value
288      */
289     applyEncoding: function(value){
290         return Ext.encode(value);
291     },
292
293     /**
294      * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
295      * this simply JSON-encodes the sorter data
296      * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
297      * @return {String} The encoded sorters
298      */
299     encodeSorters: function(sorters) {
300         var min = [],
301             length = sorters.length,
302             i = 0;
303
304         for (; i < length; i++) {
305             min[i] = {
306                 property : sorters[i].property,
307                 direction: sorters[i].direction
308             };
309         }
310         return this.applyEncoding(min);
311
312     },
313
314     /**
315      * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
316      * this simply JSON-encodes the filter data
317      * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
318      * @return {String} The encoded filters
319      */
320     encodeFilters: function(filters) {
321         var min = [],
322             length = filters.length,
323             i = 0;
324
325         for (; i < length; i++) {
326             min[i] = {
327                 property: filters[i].property,
328                 value   : filters[i].value
329             };
330         }
331         return this.applyEncoding(min);
332     },
333
334     /**
335      * @private
336      * Copy any sorters, filters etc into the params so they can be sent over the wire
337      */
338     getParams: function(operation) {
339         var me             = this,
340             params         = {},
341             isDef          = Ext.isDefined,
342             groupers       = operation.groupers,
343             sorters        = operation.sorters,
344             filters        = operation.filters,
345             page           = operation.page,
346             start          = operation.start,
347             limit          = operation.limit,
348
349             simpleSortMode = me.simpleSortMode,
350
351             pageParam      = me.pageParam,
352             startParam     = me.startParam,
353             limitParam     = me.limitParam,
354             groupParam     = me.groupParam,
355             sortParam      = me.sortParam,
356             filterParam    = me.filterParam,
357             directionParam = me.directionParam;
358
359         if (pageParam && isDef(page)) {
360             params[pageParam] = page;
361         }
362
363         if (startParam && isDef(start)) {
364             params[startParam] = start;
365         }
366
367         if (limitParam && isDef(limit)) {
368             params[limitParam] = limit;
369         }
370
371         if (groupParam && groupers && groupers.length > 0) {
372             // Grouper is a subclass of sorter, so we can just use the sorter method
373             params[groupParam] = me.encodeSorters(groupers);
374         }
375
376         if (sortParam && sorters && sorters.length > 0) {
377             if (simpleSortMode) {
378                 params[sortParam] = sorters[0].property;
379                 params[directionParam] = sorters[0].direction;
380             } else {
381                 params[sortParam] = me.encodeSorters(sorters);
382             }
383
384         }
385
386         if (filterParam && filters && filters.length > 0) {
387             params[filterParam] = me.encodeFilters(filters);
388         }
389
390         return params;
391     },
392
393     /**
394      * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
395      * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
396      * @param {Ext.data.Request} request The request object
397      * @return {String} The url
398      */
399     buildUrl: function(request) {
400         var me = this,
401             url = me.getUrl(request);
402
403         //<debug>
404         if (!url) {
405             Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
406         }
407         //</debug>
408
409         if (me.noCache) {
410             url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
411         }
412
413         return url;
414     },
415
416     /**
417      * Get the url for the request taking into account the order of priority,
418      * - The request
419      * - The api
420      * - The url
421      * @private
422      * @param {Ext.data.Request} request The request
423      * @return {String} The url
424      */
425     getUrl: function(request){
426         return request.url || this.api[request.action] || this.url;
427     },
428
429     /**
430      * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
431      * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
432      * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
433      * each of the methods that delegate to it.
434      *
435      * @param {Ext.data.Operation} operation The Ext.data.Operation object
436      * @param {Function} callback The callback function to call when the Operation has completed
437      * @param {Object} scope The scope in which to execute the callback
438      */
439     doRequest: function(operation, callback, scope) {
440         //<debug>
441         Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
442         //</debug>
443     },
444
445     /**
446      * Optional callback function which can be used to clean up after a request has been completed.
447      * @param {Ext.data.Request} request The Request object
448      * @param {Boolean} success True if the request was successful
449      * @method
450      */
451     afterRequest: Ext.emptyFn,
452
453     onDestroy: function() {
454         Ext.destroy(this.reader, this.writer);
455     }
456 });
457