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