]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/data/proxy/Server.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / data / proxy / Server.js
CommitLineData
6527f429
DM
1/**\r
2 * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and\r
3 * would not usually be used directly.\r
4 * @protected\r
5 */\r
6Ext.define('Ext.data.proxy.Server', {\r
7 extend: 'Ext.data.proxy.Proxy',\r
8 alias : 'proxy.server',\r
9 alternateClassName: 'Ext.data.ServerProxy',\r
10 uses : ['Ext.data.Request'],\r
11 \r
12 isRemote: true,\r
13\r
14 config: {\r
15 /**\r
16 * @cfg {String} url\r
17 * The URL from which to request the data object.\r
18 */\r
19 url: '',\r
20 \r
21 /**\r
22 * @cfg {String} [pageParam="page"]\r
23 * The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to `''` if you don't\r
24 * want to send a page parameter.\r
25 */\r
26 pageParam: 'page',\r
27 \r
28 /**\r
29 * @cfg {String} [startParam="start"]\r
30 * The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this to `''` if you don't\r
31 * want to send a start parameter.\r
32 */\r
33 startParam: 'start',\r
34 \r
35 /**\r
36 * @cfg {String} [limitParam="limit"]\r
37 * The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this to `''` if you don't\r
38 * want to send a limit parameter.\r
39 */\r
40 limitParam: 'limit',\r
41 \r
42 /**\r
43 * @cfg {String} [groupParam="group"]\r
44 * The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this to `''` if you don't\r
45 * want to send a group parameter.\r
46 */\r
47 groupParam: 'group',\r
48 \r
49 /**\r
50 * @cfg {String} [groupDirectionParam="groupDir"]\r
51 * The name of the direction parameter to send in a request. **This is only used when simpleGroupMode is set to\r
52 * true.**\r
53 */\r
54 groupDirectionParam: 'groupDir',\r
55 \r
56 /**\r
57 * @cfg {String} [sortParam="sort"]\r
58 * The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this to `''` if you don't\r
59 * want to send a sort parameter.\r
60 */\r
61 sortParam: 'sort',\r
62 \r
63 /**\r
64 * @cfg {String} [filterParam="filter"]\r
65 * The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set this to `''` if you don't\r
66 * want to send a filter parameter.\r
67 */\r
68 filterParam: 'filter',\r
69 \r
70 /**\r
71 * @cfg {String} [directionParam="dir"]\r
72 * The name of the direction parameter to send in a request. **This is only used when simpleSortMode is set to\r
73 * true.**\r
74 */\r
75 directionParam: 'dir',\r
76 \r
77 /**\r
78 * @cfg {String} [idParam="id"]\r
79 * The name of the parameter which carries the id of the entity being operated upon.\r
80 */\r
81 idParam: 'id',\r
82 \r
83 /**\r
84 * @cfg {Boolean} [simpleSortMode=false]\r
85 * Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a\r
86 * remote sort is requested. The {@link #directionParam} and {@link #sortParam} will be sent with the property name\r
87 * and either 'ASC' or 'DESC'.\r
88 */\r
89 simpleSortMode: false,\r
90 \r
91 /**\r
92 * @cfg {Boolean} [simpleGroupMode=false]\r
93 * Enabling simpleGroupMode in conjunction with remoteGroup will only send one group property and a direction when a\r
94 * remote group is requested. The {@link #groupDirectionParam} and {@link #groupParam} will be sent with the property name and either 'ASC'\r
95 * or 'DESC'.\r
96 */\r
97 simpleGroupMode: false,\r
98 \r
99 /**\r
100 * @cfg {Boolean} [noCache=true]\r
101 * Disable caching by adding a unique parameter name to the request. Set to false to allow caching. Defaults to true.\r
102 */\r
103 noCache : true,\r
104 \r
105 /**\r
106 * @cfg {String} [cacheString="_dc"]\r
107 * The name of the cache param added to the url when using noCache. Defaults to "_dc".\r
108 */\r
109 cacheString: "_dc",\r
110 \r
111 /**\r
112 * @cfg {Number} timeout\r
113 * The number of milliseconds to wait for a response. Defaults to 30000 milliseconds (30 seconds).\r
114 */\r
115 timeout : 30000,\r
116 \r
117 /**\r
118 * @cfg {Object} api\r
119 * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:\r
120 *\r
121 * api: {\r
122 * create : undefined,\r
123 * read : undefined,\r
124 * update : undefined,\r
125 * destroy : undefined\r
126 * }\r
127 *\r
128 * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate\r
129 * {@link #api} property, or if undefined default to the configured\r
130 * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.\r
131 *\r
132 * For example:\r
133 *\r
134 * api: {\r
135 * create : '/controller/new',\r
136 * read : '/controller/load',\r
137 * update : '/controller/update',\r
138 * destroy : '/controller/destroy_action'\r
139 * }\r
140 *\r
141 * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the\r
142 * configured {@link Ext.data.proxy.Server#url url}.\r
143 */\r
144 api: {\r
145 create : undefined,\r
146 read : undefined,\r
147 update : undefined,\r
148 destroy : undefined\r
149 },\r
150\r
151 /**\r
152 * @cfg {Object} extraParams\r
153 * Extra parameters that will be included on every request. Individual requests with params of the same name\r
154 * will override these params when they are in conflict.\r
155 */\r
156 extraParams: {}\r
157 },\r
158\r
159 /**\r
160 * @event exception\r
161 * Fires when the server returns an exception. This event may also be listened\r
162 * to in the event that a request has timed out or has been aborted.\r
163 * @param {Ext.data.proxy.Proxy} this\r
164 * @param {Ext.data.Request} request The request that was sent\r
165 * @param {Ext.data.operation.Operation} operation The operation that triggered the request\r
166 */\r
167\r
168 //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case\r
169 create: function() {\r
170 return this.doRequest.apply(this, arguments);\r
171 },\r
172\r
173 read: function() {\r
174 return this.doRequest.apply(this, arguments);\r
175 },\r
176\r
177 update: function() {\r
178 return this.doRequest.apply(this, arguments);\r
179 },\r
180\r
181 erase: function() {\r
182 return this.doRequest.apply(this, arguments);\r
183 },\r
184\r
185 /**\r
186 * Sets a value in the underlying {@link #extraParams}.\r
187 * @param {String} name The key for the new value\r
188 * @param {Object} value The value\r
189 */\r
190 setExtraParam: function(name, value) {\r
191 var extraParams = this.getExtraParams();\r
192 extraParams[name] = value;\r
193 this.fireEvent('extraparamschanged', extraParams);\r
194 },\r
195\r
196 updateExtraParams: function(newExtraParams, oldExtraParams) {\r
197 this.fireEvent('extraparamschanged', newExtraParams);\r
198 },\r
199\r
200 /**\r
201 * Creates an {@link Ext.data.Request Request} object from {@link Ext.data.operation.Operation Operation}.\r
202 *\r
203 * This gets called from doRequest methods in subclasses of Server proxy.\r
204 * \r
205 * @param {Ext.data.operation.Operation} operation The operation to execute\r
206 * @return {Ext.data.Request} The request object\r
207 */\r
208 buildRequest: function(operation) {\r
209 var me = this,\r
210 initialParams = Ext.apply({}, operation.getParams()),\r
211 // Clone params right now so that they can be mutated at any point further down the call stack\r
212 params = Ext.applyIf(initialParams, me.getExtraParams() || {}),\r
213 request,\r
214 operationId,\r
215 idParam;\r
216\r
217 //copy any sorters, filters etc into the params so they can be sent over the wire\r
218 Ext.applyIf(params, me.getParams(operation));\r
219\r
220 // Set up the entity id parameter according to the configured name.\r
221 // This defaults to "id". But TreeStore has a "nodeParam" configuration which\r
222 // specifies the id parameter name of the node being loaded.\r
223 operationId = operation.getId();\r
224 idParam = me.getIdParam();\r
225 if (operationId !== undefined && params[idParam] === undefined) {\r
226 params[idParam] = operationId;\r
227 }\r
228\r
229 request = new Ext.data.Request({\r
230 params : params,\r
231 action : operation.getAction(),\r
232 records : operation.getRecords(),\r
233 url : operation.getUrl(),\r
234 operation: operation,\r
235\r
236 // this is needed by JsonSimlet in order to properly construct responses for\r
237 // requests from this proxy\r
238 proxy: me\r
239 });\r
240\r
241 request.setUrl(me.buildUrl(request));\r
242\r
243 /*\r
244 * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the\r
245 * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing\r
246 */\r
247 operation.setRequest(request);\r
248\r
249 return request;\r
250 },\r
251\r
252 /**\r
253 * Processes response, which may involve updating or committing records, each of which\r
254 * will inform the owning stores and their interested views. Finally, we may perform\r
255 * an additional layout if the data shape has changed. \r
256 *\r
257 * @protected\r
258 */\r
259 processResponse: function(success, operation, request, response) {\r
260 var me = this,\r
261 exception, reader, resultSet;\r
262\r
263 // Processing a response may involve updating or committing many records\r
264 // each of which will inform the owning stores, which will ultimately\r
265 // inform interested views which will most likely have to do a layout\r
266 // assuming that the data shape has changed.\r
267 // Bracketing the processing with this event gives owning stores the ability\r
268 // to fire their own beginupdate/endupdate events which can be used by interested\r
269 // views to suspend layouts.\r
270 me.fireEvent('beginprocessresponse', me, response, operation);\r
271\r
272 if (success === true) {\r
273 reader = me.getReader();\r
274\r
275 if (response.status === 204) {\r
276 resultSet = reader.getNullResultSet();\r
277 } else {\r
278 resultSet = reader.read(me.extractResponseData(response), {\r
279 // If we're doing an update, we want to construct the models ourselves.\r
280 recordCreator: operation.getRecordCreator()\r
281 });\r
282 }\r
283\r
284 operation.process(resultSet, request, response);\r
285 exception = !operation.wasSuccessful();\r
286 } else {\r
287 me.setException(operation, response);\r
288 exception = true;\r
289 }\r
290 \r
291 if (exception) {\r
292 me.fireEvent('exception', me, response, operation);\r
293 }\r
294\r
295 me.afterRequest(request, success);\r
296\r
297 // Tell owning store processing has finished.\r
298 // It will fire its endupdate event which will cause interested views to \r
299 // resume layouts.\r
300 me.fireEvent('endprocessresponse', me, response, operation);\r
301 },\r
302 \r
303 /**\r
304 * Sets up an exception on the operation\r
305 * @private\r
306 * @param {Ext.data.operation.Operation} operation The operation\r
307 * @param {Object} response The response\r
308 */\r
309 setException: function(operation, response) {\r
310 operation.setException({\r
311 status: response.status,\r
312 statusText: response.statusText,\r
313 response: response\r
314 });\r
315 },\r
316\r
317 /**\r
318 * @method\r
319 * Template method to allow subclasses to specify how to get the response for the reader.\r
320 * @template\r
321 * @private\r
322 * @param {Object} response The server response\r
323 * @return {Object} The response data to be used by the reader\r
324 */\r
325 extractResponseData: Ext.identityFn,\r
326\r
327 /**\r
328 * Encode any values being sent to the server. Can be overridden in subclasses.\r
329 * @protected\r
330 * @param {Array} value An array of sorters/filters.\r
331 * @return {Object} The encoded value\r
332 */\r
333 applyEncoding: function(value) {\r
334 return Ext.encode(value);\r
335 },\r
336\r
337 /**\r
338 * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,\r
339 * this simply JSON-encodes the sorter data\r
340 * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects\r
341 * @param {Boolean} [preventArray=false] Prevents the items from being output as an array.\r
342 * @return {String} The encoded sorters\r
343 */\r
344 encodeSorters: function(sorters, preventArray) {\r
345 var out = [],\r
346 length = sorters.length, \r
347 i;\r
348 \r
349 for (i = 0; i < length; i++) {\r
350 out[i] = sorters[i].serialize();\r
351 }\r
352\r
353 return this.applyEncoding(preventArray ? out[0] : out);\r
354 },\r
355\r
356 /**\r
357 * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,\r
358 * this simply JSON-encodes the filter data\r
359 * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects\r
360 * @return {String} The encoded filters\r
361 */\r
362 encodeFilters: function(filters) {\r
363 var out = [],\r
364 length = filters.length,\r
365 i, op;\r
366\r
367 for (i = 0; i < length; i++) {\r
368 out[i] = filters[i].serialize();\r
369 }\r
370\r
371 return this.applyEncoding(out);\r
372 },\r
373\r
374 /**\r
375 * @private\r
376 * Copy any sorters, filters etc into the params so they can be sent over the wire\r
377 */\r
378 getParams: function(operation) {\r
379 if (!operation.isReadOperation) {\r
380 return {};\r
381 }\r
382\r
383 var me = this,\r
384 params = {},\r
385 grouper = operation.getGrouper(),\r
386 sorters = operation.getSorters(),\r
387 filters = operation.getFilters(),\r
388 page = operation.getPage(),\r
389 start = operation.getStart(),\r
390 limit = operation.getLimit(),\r
391 simpleSortMode = me.getSimpleSortMode(),\r
392 simpleGroupMode = me.getSimpleGroupMode(),\r
393 pageParam = me.getPageParam(),\r
394 startParam = me.getStartParam(),\r
395 limitParam = me.getLimitParam(),\r
396 groupParam = me.getGroupParam(),\r
397 groupDirectionParam = me.getGroupDirectionParam(),\r
398 sortParam = me.getSortParam(),\r
399 filterParam = me.getFilterParam(),\r
400 directionParam = me.getDirectionParam(),\r
401 hasGroups, index;\r
402\r
403 if (pageParam && page) {\r
404 params[pageParam] = page;\r
405 }\r
406\r
407 if (startParam && (start || start === 0)) {\r
408 params[startParam] = start;\r
409 }\r
410\r
411 if (limitParam && limit) {\r
412 params[limitParam] = limit;\r
413 }\r
414\r
415 hasGroups = groupParam && grouper;\r
416 if (hasGroups) {\r
417 // Grouper is a subclass of sorter, so we can just use the sorter method\r
418 if (simpleGroupMode) {\r
419 params[groupParam] = grouper.getProperty();\r
420 params[groupDirectionParam] = grouper.getDirection();\r
421 } else {\r
422 params[groupParam] = me.encodeSorters([grouper], true);\r
423 }\r
424 }\r
425\r
426 if (sortParam && sorters && sorters.length > 0) {\r
427 if (simpleSortMode) {\r
428 index = 0;\r
429 // Group will be included in sorters, so grab the next one\r
430 if (sorters.length > 1 && hasGroups) {\r
431 index = 1;\r
432 }\r
433 params[sortParam] = sorters[index].getProperty();\r
434 params[directionParam] = sorters[index].getDirection();\r
435 } else {\r
436 params[sortParam] = me.encodeSorters(sorters);\r
437 }\r
438\r
439 }\r
440\r
441 if (filterParam && filters && filters.length > 0) {\r
442 params[filterParam] = me.encodeFilters(filters);\r
443 }\r
444\r
445 return params;\r
446 },\r
447\r
448 /**\r
449 * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the\r
450 * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.\r
451 * @param {Ext.data.Request} request The request object\r
452 * @return {String} The url\r
453 */\r
454 buildUrl: function(request) {\r
455 var me = this,\r
456 url = me.getUrl(request);\r
457\r
458 //<debug>\r
459 if (!url) {\r
460 Ext.raise("You are using a ServerProxy but have not supplied it with a url.");\r
461 }\r
462 //</debug>\r
463\r
464 if (me.getNoCache()) {\r
465 url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.getCacheString(), Ext.Date.now()));\r
466 }\r
467\r
468 return url;\r
469 },\r
470\r
471 /**\r
472 * Get the url for the request taking into account the order of priority,\r
473 * - The request\r
474 * - The api\r
475 * - The url\r
476 * @private\r
477 * @param {Ext.data.Request} request The request\r
478 * @return {String} The url\r
479 */\r
480 getUrl: function(request) {\r
481 var url;\r
482 if (request) {\r
483 url = request.getUrl() || this.getApi()[request.getAction()];\r
484 }\r
485 return url ? url : this.callParent();\r
486 },\r
487\r
488 /**\r
489 * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #erase} methods all\r
490 * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link\r
491 * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as\r
492 * each of the methods that delegate to it.\r
493 *\r
494 * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object\r
495 * @param {Function} callback The callback function to call when the Operation has completed\r
496 * @param {Object} scope The scope in which to execute the callback\r
497 */\r
498 doRequest: function(operation) {\r
499 //<debug>\r
500 Ext.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");\r
501 //</debug>\r
502 },\r
503\r
504 /**\r
505 * Optional callback function which can be used to clean up after a request has been completed.\r
506 * @param {Ext.data.Request} request The Request object\r
507 * @param {Boolean} success True if the request was successful\r
508 * @protected\r
509 * @template\r
510 * @method\r
511 */\r
512 afterRequest: Ext.emptyFn,\r
513\r
514 destroy: function() {\r
515 this.callParent();\r
516 \r
517 Ext.destroy(this.getReader(), this.getWriter());\r
518 \r
519 this.reader = this.writer = null;\r
520 }\r
521});\r