]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/data/ProxyStore.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / data / ProxyStore.js
CommitLineData
6527f429
DM
1/**\r
2 * ProxyStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.BufferedStore}. It's never used directly,\r
3 * but offers a set of methods used by both of those subclasses.\r
4 *\r
5 * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what\r
6 * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what\r
7 * ProxyStore is and is not.\r
8 *\r
9 * ProxyStore provides the basic configuration for anything that can be considered a Store. It expects to be\r
10 * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a\r
11 * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.\r
12 *\r
13 * ProxyStore provides a few helpful methods such as {@link #method-load} and {@link #sync}, which load and save data\r
14 * respectively, passing the requests through the configured {@link #proxy}.\r
15 *\r
16 * Built-in Store subclasses add extra behavior to each of these functions. Note also that each ProxyStore subclass\r
17 * has its own way of storing data - in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.Collection Collection},\r
18 * whereas in {@link Ext.data.BufferedStore BufferedStore} we use a {@link Ext.data.PageMap} to maintain a client side cache of pages of records.\r
19 *\r
20 * The store provides filtering and sorting support. This sorting/filtering can happen on the client side\r
21 * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and\r
22 * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #method-sort} and\r
23 * {@link Ext.data.Store#filter filter} methods.\r
24 */\r
25Ext.define('Ext.data.ProxyStore', {\r
26 extend: 'Ext.data.AbstractStore',\r
27\r
28 requires: [\r
29 'Ext.data.Model',\r
30 'Ext.data.proxy.Proxy',\r
31 'Ext.data.proxy.Memory',\r
32 'Ext.data.operation.*'\r
33 ],\r
34\r
35 config: {\r
36 // @cmd-auto-dependency {aliasPrefix: "model.", mvc: true, blame: "all"}\r
37 /**\r
38 * @cfg {String/Ext.data.Model} model\r
39 * Name of the {@link Ext.data.Model Model} associated with this store. See\r
40 * {@link Ext.data.Model#entityName}.\r
41 *\r
42 * May also be the actual Model subclass.\r
43 *\r
44 * This config is required for the store to be able to read data unless you have defined\r
45 * the {@link #fields} config which will create an anonymous `Ext.data.Model`.\r
46 */\r
47 model: undefined,\r
48\r
49 // @cmd-auto-dependency {aliasPrefix: "data.field."}\r
50 /**\r
51 * @cfg {Object[]/String[]} fields\r
52 * @inheritdoc Ext.data.Model#cfg-fields\r
53 * \r
54 * @localdoc **Note:** In general, this configuration option should only be used \r
55 * for simple stores like a two-field store of \r
56 * {@link Ext.form.field.ComboBox ComboBox}. For anything more complicated, such \r
57 * as specifying a particular id property or associations, a \r
58 * {@link Ext.data.Model Model} should be defined and specified for the \r
59 * {@link #model} config.\r
60 * \r
61 * @since 2.3.0\r
62 */\r
63 fields: null,\r
64\r
65 // @cmd-auto-dependency {aliasPrefix : "proxy."}\r
66 /**\r
67 * @cfg {String/Ext.data.proxy.Proxy/Object} proxy\r
68 * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -\r
69 * see {@link #setProxy} for details.\r
70 * @since 1.1.0\r
71 */\r
72 proxy: undefined,\r
73\r
74 /**\r
75 * @cfg {Boolean/Object} autoLoad\r
76 * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called\r
77 * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.\r
78 *\r
79 * It's important to note that {@link Ext.data.TreeStore Tree Stores} will \r
80 * load regardless of autoLoad's value if expand is set to true on the \r
81 * {@link Ext.data.TreeStore#root root node}.\r
82 * \r
83 * @since 2.3.0\r
84 */\r
85 autoLoad: undefined,\r
86\r
87 /**\r
88 * @cfg {Boolean} autoSync\r
89 * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.\r
90 */\r
91 autoSync: false,\r
92\r
93 /**\r
94 * @cfg {String} batchUpdateMode\r
95 * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's\r
96 * internal representation of the data after each operation of the batch has completed, 'complete' will wait until\r
97 * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local\r
98 * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.\r
99 */\r
100 batchUpdateMode: 'operation',\r
101 \r
102 /**\r
103 * @cfg {Boolean} sortOnLoad\r
104 * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.\r
105 * Defaults to true, ignored if {@link Ext.data.Store#remoteSort remoteSort} is true\r
106 */\r
107 sortOnLoad: true,\r
108\r
109 /**\r
110 * @cfg {Boolean} [trackRemoved=true]\r
111 * This config controls whether removed records are remembered by this store for\r
112 * later saving to the server.\r
113 */\r
114 trackRemoved: true,\r
115\r
116 /**\r
117 * @cfg {Boolean} [asynchronousLoad]\r
118 * This defaults to `true` when this store's {@link #cfg-proxy} is asynchronous, such as an\r
119 * {@link Ext.data.proxy.Ajax Ajax proxy}.\r
120 *\r
121 * When the proxy is synchronous, such as a {@link Ext.data.proxy.Memory} memory proxy, this\r
122 * defaults to `false`.\r
123 *\r
124 * *NOTE:* This does not cause synchronous Ajax requests if configured `false` when an Ajax proxy\r
125 * is used. It causes immediate issuing of an Ajax request when {@link #method-load} is called\r
126 * rather than issuing the request at the end of the current event handler run.\r
127 *\r
128 * What this means is that when using an Ajax proxy, calls to \r
129 * {@link #method-load} do not fire the request to the remote resource \r
130 * immediately, but schedule a request to be made. This is so that multiple \r
131 * requests are not fired when mutating a store's remote filters and sorters (as \r
132 * happens during state restoration). The request is made only once after all \r
133 * relevant store state is fully set.\r
134 *\r
135 * @since 6.0.1\r
136 */\r
137 asynchronousLoad: undefined\r
138 },\r
139\r
140 onClassExtended: function(cls, data, hooks) {\r
141 var model = data.model,\r
142 onBeforeClassCreated;\r
143\r
144 if (typeof model === 'string') {\r
145 onBeforeClassCreated = hooks.onBeforeCreated;\r
146\r
147 hooks.onBeforeCreated = function() {\r
148 var me = this,\r
149 args = arguments;\r
150\r
151 Ext.require(model, function() {\r
152 onBeforeClassCreated.apply(me, args);\r
153 });\r
154 };\r
155 }\r
156 },\r
157\r
158 /**\r
159 * @private\r
160 * @property {Boolean}\r
161 * The class name of the model that this store uses if no explicit {@link #model} is given\r
162 */\r
163 implicitModel: 'Ext.data.Model',\r
164\r
165 /**\r
166 * @property {Object} lastOptions\r
167 * Property to hold the last options from a {@link #method-load} method call. This object is used for the {@link #method-reload}\r
168 * to reuse the same options. Please see {@link #method-reload} for a simple example on how to use the lastOptions property.\r
169 */\r
170\r
171 /**\r
172 * @property {Number} autoSyncSuspended\r
173 * A counter to track suspensions.\r
174 * @private\r
175 */\r
176 autoSyncSuspended: 0,\r
177\r
178 //documented above\r
179 constructor: function(config) {\r
180 var me = this;\r
181\r
182 // <debug>\r
183 var configModel = me.model;\r
184 // </debug>\r
185\r
186 /**\r
187 * @event beforeload\r
188 * Fires before a request is made for a new data object. If the beforeload handler returns false the load\r
189 * action will be canceled.\r
190 * @param {Ext.data.Store} store This Store\r
191 * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to\r
192 * load the Store\r
193 * @since 1.1.0\r
194 */\r
195\r
196 /**\r
197 * @event load\r
198 * Fires whenever the store reads data from a remote data source.\r
199 * @param {Ext.data.Store} this\r
200 * @param {Ext.data.Model[]} records An array of records\r
201 * @param {Boolean} successful True if the operation was successful.\r
202 * @param {Ext.data.operation.Read} operation The \r
203 * {@link Ext.data.operation.Read Operation} object that was used in the data \r
204 * load call\r
205 * @since 1.1.0\r
206 */\r
207\r
208 /**\r
209 * @event write\r
210 * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}\r
211 * @param {Ext.data.Store} store This Store\r
212 * @param {Ext.data.operation.Operation} operation The {@link Ext.data.operation.Operation Operation} object that was used in\r
213 * the write\r
214 * @since 3.4.0\r
215 */\r
216\r
217 /**\r
218 * @event beforesync\r
219 * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the sync\r
220 * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy\r
221 */\r
222\r
223 /**\r
224 * @event metachange\r
225 * Fires when this store's underlying reader (available via the proxy) provides new metadata.\r
226 * Metadata usually consists of new field definitions, but can include any configuration data\r
227 * required by an application, and can be processed as needed in the event handler.\r
228 * This event is currently only fired for JsonReaders.\r
229 * @param {Ext.data.Store} this\r
230 * @param {Object} meta The JSON metadata\r
231 * @since 1.1.0\r
232 */\r
233\r
234\r
235 /**\r
236 * Temporary cache in which removed model instances are kept until successfully\r
237 * synchronised with a Proxy, at which point this is cleared.\r
238 *\r
239 * This cache is maintained unless you set `trackRemoved` to `false`.\r
240 *\r
241 * @protected\r
242 * @property {Ext.data.Model[]} removed\r
243 */\r
244 me.removed = [];\r
245\r
246 me.callParent(arguments);\r
247\r
248 if (me.getAsynchronousLoad() === false) {\r
249 me.flushLoad();\r
250 }\r
251\r
252 // <debug>\r
253 if (!me.getModel() && me.useModelWarning !== false && me.getStoreId() !== 'ext-empty-store') {\r
254 // There are a number of ways things could have gone wrong, try to give as much information as possible\r
255 var logMsg = [\r
256 Ext.getClassName(me) || 'Store',\r
257 ' created with no model.'\r
258 ];\r
259\r
260 if (typeof configModel === 'string') {\r
261 logMsg.push(" The name '", configModel, "'", ' does not correspond to a valid model.');\r
262 }\r
263\r
264 Ext.log.warn(logMsg.join(''));\r
265 }\r
266 // </debug>\r
267 },\r
268\r
269 applyAsynchronousLoad: function(asynchronousLoad) {\r
270 // Default in an asynchronousLoad setting.\r
271 // It defaults to false if the proxy is synchronous, and true if the proxy is asynchronous.\r
272 if (asynchronousLoad == null) {\r
273 asynchronousLoad = !this.loadsSynchronously();\r
274 }\r
275 return asynchronousLoad;\r
276 },\r
277\r
278 updateAutoLoad: function(autoLoad) {\r
279 // Ensure the data collection is set up\r
280 this.getData();\r
281 if (autoLoad) {\r
282 // Defer the load until idle, when the store (and probably the view) is fully constructed\r
283 this.load(Ext.isObject(autoLoad) ? autoLoad : undefined);\r
284 }\r
285 },\r
286\r
287 /**\r
288 * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}\r
289 * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the\r
290 * number of records loaded into the Store at the moment, getTotalCount returns the number of records that\r
291 * could be loaded into the Store if the Store contained all data\r
292 * @return {Number} The total number of Model instances available via the Proxy. 0 returned if\r
293 * no value has been set via the reader.\r
294 */\r
295 getTotalCount: function() {\r
296 return this.totalCount || 0;\r
297 },\r
298\r
299 applyFields: function(fields) {\r
300 if (fields) {\r
301 this.createImplicitModel(fields);\r
302 }\r
303 },\r
304\r
305 applyModel: function(model) {\r
306 if (model) {\r
307 model = Ext.data.schema.Schema.lookupEntity(model);\r
308 }\r
309 // If no model, ensure that the fields config is converted to a model.\r
310 else {\r
311 this.getFields();\r
312 model = this.getModel() || this.createImplicitModel();\r
313 }\r
314 return model;\r
315 },\r
316\r
317 applyProxy: function(proxy) {\r
318 var model = this.getModel();\r
319\r
320 if (proxy !== null) {\r
321 if (proxy) {\r
322 if (proxy.isProxy) {\r
323 proxy.setModel(model);\r
324 } else {\r
325 if (Ext.isString(proxy)) {\r
326 proxy = {\r
327 type: proxy,\r
328 model: model\r
329 };\r
330 } else if (!proxy.model) {\r
331 proxy = Ext.apply({\r
332 model: model\r
333 }, proxy);\r
334 }\r
335\r
336 proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);\r
337 proxy.autoCreated = true;\r
338 }\r
339 } else if (model) {\r
340 proxy = model.getProxy();\r
341 }\r
342 \r
343 if (!proxy) {\r
344 proxy = Ext.createByAlias('proxy.memory');\r
345 proxy.autoCreated = true;\r
346 }\r
347 }\r
348\r
349 return proxy;\r
350 },\r
351\r
352 applyState: function (state) {\r
353 var me = this;\r
354\r
355 me.callParent([state]);\r
356\r
357 // This is called during construction. Sorters and filters might have changed\r
358 // which require a reload.\r
359 // If autoLoad is true, it might have loaded synchronously from a memory proxy, so needs to reload.\r
360 // If it is already loaded, we definitely need to reload to apply the state.\r
361 if (me.getAutoLoad() || me.isLoaded()) {\r
362 me.load();\r
363 }\r
364 },\r
365\r
366 updateProxy: function(proxy, oldProxy) {\r
367 this.proxyListeners = Ext.destroy(this.proxyListeners);\r
368 },\r
369\r
370 updateTrackRemoved: function (track) {\r
371 this.cleanRemoved();\r
372 this.removed = track ? [] : null;\r
373 },\r
374\r
375 /**\r
376 * @private\r
377 */\r
378 onMetaChange: function(proxy, meta) {\r
379 this.fireEvent('metachange', this, meta);\r
380 },\r
381\r
382 //saves any phantom records\r
383 create: function(data, options) {\r
384 var me = this,\r
385 Model = me.getModel(),\r
386 instance = new Model(data),\r
387 operation;\r
388\r
389 options = Ext.apply({}, options);\r
390 if (!options.records) {\r
391 options.records = [instance];\r
392 }\r
393 options.internalScope = me;\r
394 options.internalCallback = me.onProxyWrite;\r
395\r
396 operation = me.createOperation('create', options);\r
397 return operation.execute();\r
398 },\r
399\r
400 read: function() {\r
401 return this.load.apply(this, arguments);\r
402 },\r
403\r
404 update: function(options) {\r
405 var me = this,\r
406 operation;\r
407 \r
408 options = Ext.apply({}, options);\r
409 if (!options.records) {\r
410 options.records = me.getUpdatedRecords();\r
411 }\r
412 options.internalScope = me;\r
413 options.internalCallback = me.onProxyWrite;\r
414\r
415 operation = me.createOperation('update', options);\r
416 return operation.execute();\r
417 },\r
418\r
419 /**\r
420 * @private\r
421 * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect\r
422 * the updates provided by the Proxy\r
423 */\r
424 onProxyWrite: function(operation) {\r
425 var me = this,\r
426 success = operation.wasSuccessful(),\r
427 records = operation.getRecords();\r
428\r
429 switch (operation.getAction()) {\r
430 case 'create':\r
431 me.onCreateRecords(records, operation, success);\r
432 break;\r
433 case 'update':\r
434 me.onUpdateRecords(records, operation, success);\r
435 break;\r
436 case 'destroy':\r
437 me.onDestroyRecords(records, operation, success);\r
438 break;\r
439 }\r
440\r
441 if (success) {\r
442 me.fireEvent('write', me, operation);\r
443 me.fireEvent('datachanged', me);\r
444 }\r
445 },\r
446 \r
447 // may be implemented by store subclasses\r
448 onCreateRecords: Ext.emptyFn,\r
449 \r
450 // may be implemented by store subclasses\r
451 onUpdateRecords: Ext.emptyFn,\r
452 \r
453 /**\r
454 * Removes any records when a write is returned from the server.\r
455 * @private\r
456 * @param {Ext.data.Model[]} records The array of removed records\r
457 * @param {Ext.data.operation.Operation} operation The operation that just completed\r
458 * @param {Boolean} success True if the operation was successful\r
459 */\r
460 onDestroyRecords: function(records, operation, success) {\r
461 if (success) {\r
462 this.cleanRemoved();\r
463 }\r
464 },\r
465\r
466 // tells the attached proxy to destroy the given records\r
467 // @since 3.4.0\r
468 erase: function(options) {\r
469 var me = this,\r
470 operation;\r
471\r
472 options = Ext.apply({}, options);\r
473 if (!options.records) {\r
474 options.records = me.getRemovedRecords();\r
475 }\r
476 options.internalScope = me;\r
477 options.internalCallback = me.onProxyWrite;\r
478\r
479 operation = me.createOperation('destroy', options);\r
480 return operation.execute();\r
481 },\r
482\r
483 /**\r
484 * @private\r
485 * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through\r
486 * to onProxyWrite.\r
487 */\r
488 onBatchOperationComplete: function(batch, operation) {\r
489 return this.onProxyWrite(operation);\r
490 },\r
491\r
492 /**\r
493 * @private\r
494 * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations\r
495 * and updates the Store's internal data MixedCollection.\r
496 */\r
497 onBatchComplete: function(batch, operation) {\r
498 var me = this,\r
499 operations = batch.operations,\r
500 length = operations.length,\r
501 i;\r
502\r
503 if (me.batchUpdateMode !== 'operation') {\r
504 me.suspendEvents();\r
505\r
506 for (i = 0; i < length; i++) {\r
507 me.onProxyWrite(operations[i]);\r
508 }\r
509\r
510 me.resumeEvents();\r
511 }\r
512\r
513 me.isSyncing = false;\r
514 me.fireEvent('datachanged', me);\r
515 },\r
516\r
517 /**\r
518 * @private\r
519 */\r
520 onBatchException: function(batch, operation) {\r
521 // //decide what to do... could continue with the next operation\r
522 // batch.start();\r
523 //\r
524 // //or retry the last operation\r
525 // batch.retry();\r
526 },\r
527\r
528 /**\r
529 * @private\r
530 * Filter function for new records.\r
531 */\r
532 filterNew: function(item) {\r
533 // only want phantom records that are valid\r
534 return item.phantom === true && item.isValid();\r
535 },\r
536\r
537 /**\r
538 * Returns all `{@link Ext.data.Model#property-phantom phantom}` records in this store.\r
539 * @return {Ext.data.Model[]} A possibly empty array of `phantom` records.\r
540 */\r
541 getNewRecords: function() {\r
542 return [];\r
543 },\r
544\r
545 /**\r
546 * Returns all valid, non-phantom Model instances that have been updated in the Store but not yet synchronized with the Proxy.\r
547 * @return {Ext.data.Model[]} The updated Model instances\r
548 */\r
549 getUpdatedRecords: function() {\r
550 return [];\r
551 },\r
552\r
553 /**\r
554 * Gets all {@link Ext.data.Model records} added or updated since the last commit. Note that the order of records\r
555 * returned is not deterministic and does not indicate the order in which records were modified. Note also that\r
556 * removed records are not included (use {@link #getRemovedRecords} for that).\r
557 * @return {Ext.data.Model[]} The added and updated Model instances\r
558 */\r
559 getModifiedRecords : function(){\r
560 return [].concat(this.getNewRecords(), this.getUpdatedRecords());\r
561 },\r
562 \r
563 /**\r
564 * @private\r
565 * Filter function for updated records.\r
566 */\r
567 filterUpdated: function(item) {\r
568 // only want dirty records, not phantoms that are valid\r
569 return item.dirty === true && item.phantom !== true && item.isValid();\r
570 },\r
571\r
572 /**\r
573 * Returns any records that have been removed from the store but not yet destroyed on the proxy.\r
574 * @return {Ext.data.Model[]} The removed Model instances. Note that this is a *copy* of the store's\r
575 * array, so may be mutated.\r
576 */\r
577 getRemovedRecords: function() {\r
578 var removed = this.getRawRemovedRecords();\r
579 // If trackRemoved: false, removed will be null\r
580 return removed ? Ext.Array.clone(removed) : removed;\r
581 },\r
582\r
583 /**\r
584 * Synchronizes the store with its {@link #proxy}. This asks the proxy to batch together any new, updated\r
585 * and deleted records in the store, updating the store's internal representation of the records\r
586 * as each operation completes.\r
587 * \r
588 * @param {Object} [options] Object containing one or more properties supported by the sync method (these get \r
589 * passed along to the underlying proxy's {@link Ext.data.Proxy#batch batch} method):\r
590 * \r
591 * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply \r
592 * to the created batch). If unspecified a default batch will be auto-created as needed.\r
593 * \r
594 * @param {Function} [options.callback] The function to be called upon completion of the sync.\r
595 * The callback is called regardless of success or failure and is passed the following parameters:\r
596 * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,\r
597 * containing all operations in their current state after processing\r
598 * @param {Object} options.callback.options The options argument that was originally passed into sync\r
599 * \r
600 * @param {Function} [options.success] The function to be called upon successful completion of the sync. The \r
601 * success function is called only if no exceptions were reported in any operations. If one or more exceptions\r
602 * occurred then the failure function will be called instead. The success function is called \r
603 * with the following parameters:\r
604 * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,\r
605 * containing all operations in their current state after processing\r
606 * @param {Object} options.success.options The options argument that was originally passed into sync\r
607 * \r
608 * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the sync. The \r
609 * failure function is called when one or more operations returns an exception during processing (even if some\r
610 * operations were also successful). In this case you can check the batch's {@link Ext.data.Batch#exceptions \r
611 * exceptions} array to see exactly which operations had exceptions. The failure function is called with the \r
612 * following parameters:\r
613 * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch} that was processed, containing all\r
614 * operations in their current state after processing\r
615 * @param {Object} options.failure.options The options argument that was originally passed into sync\r
616 * \r
617 * @param {Object} [options.params] Additional params to send during the sync Operation(s).\r
618 *\r
619 * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside\r
620 * the callback, success and/or failure functions). Defaults to the store's proxy.\r
621 * \r
622 * @return {Ext.data.Store} this\r
623 */\r
624 sync: function(options) {\r
625 var me = this,\r
626 operations = {},\r
627 toCreate = me.getNewRecords(),\r
628 toUpdate = me.getUpdatedRecords(),\r
629 toDestroy = me.getRemovedRecords(),\r
630 needsSync = false;\r
631\r
632 //<debug>\r
633 if (me.isSyncing) {\r
634 Ext.log.warn('Sync called while a sync operation is in progress. Consider configuring autoSync as false.');\r
635 }\r
636 //</debug>\r
637\r
638 me.needsSync = false;\r
639\r
640 if (toCreate.length > 0) {\r
641 operations.create = toCreate;\r
642 needsSync = true;\r
643 }\r
644\r
645 if (toUpdate.length > 0) {\r
646 operations.update = toUpdate;\r
647 needsSync = true;\r
648 }\r
649\r
650 if (toDestroy.length > 0) {\r
651 operations.destroy = toDestroy;\r
652 needsSync = true;\r
653 }\r
654\r
655 if (needsSync && me.fireEvent('beforesync', operations) !== false) {\r
656 me.isSyncing = true;\r
657\r
658 options = options || {};\r
659\r
660 me.proxy.batch(Ext.apply(options, {\r
661 operations: operations,\r
662 listeners: me.getBatchListeners()\r
663 }));\r
664 }\r
665\r
666 return me;\r
667 },\r
668 \r
669 /**\r
670 * @private\r
671 * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.\r
672 * This is broken out into a separate function to allow for customisation of the listeners\r
673 * @return {Object} The listeners object\r
674 */\r
675 getBatchListeners: function() {\r
676 var me = this,\r
677 listeners = {\r
678 scope: me,\r
679 exception: me.onBatchException,\r
680 complete: me.onBatchComplete\r
681 };\r
682\r
683 if (me.batchUpdateMode === 'operation') {\r
684 listeners.operationcomplete = me.onBatchOperationComplete;\r
685 }\r
686\r
687 return listeners;\r
688 },\r
689\r
690 /**\r
691 * Saves all pending changes via the configured {@link #proxy}. Use {@link #sync} instead.\r
692 * @deprecated 4.0.0 Will be removed in the next major version\r
693 */\r
694 save: function() {\r
695 return this.sync.apply(this, arguments);\r
696 },\r
697\r
698 /**\r
699 * Marks this store as needing a load. When the current executing event handler exits,\r
700 * this store will send a request to load using its configured {@link #proxy}.\r
701 *\r
702 * Upon return of the data from whatever data source the proxy connected to, the retrieved\r
703 * {@link Ext.data.Model records} will be loaded into this store, and the optional callback will be called.\r
704 * Example usage:\r
705 *\r
706 * store.load({\r
707 * scope: this,\r
708 * callback: function(records, operation, success) {\r
709 * // the {@link Ext.data.operation.Operation operation} object\r
710 * // contains all of the details of the load operation\r
711 * console.log(records);\r
712 * }\r
713 * });\r
714 *\r
715 * If the callback scope does not need to be set, a function can simply be passed:\r
716 *\r
717 * store.load(function(records, operation, success) {\r
718 * console.log('loaded records');\r
719 * });\r
720 *\r
721 * @param {Object} [options] This is passed into the {@link Ext.data.operation.Operation Operation}\r
722 * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.\r
723 * In addition to the options listed below, this object may contain properties to configure the\r
724 * {@link Ext.data.operation.Operation Operation}.\r
725 * @param {Function} [options.callback] A function which is called when the response arrives.\r
726 * @param {Ext.data.Model[]} options.callback.records Array of records.\r
727 * @param {Ext.data.operation.Operation} options.callback.operation The Operation itself.\r
728 * @param {Boolean} options.callback.success `true` when operation completed successfully.\r
729 * @param {Boolean} [options.addRecords=false] Specify as `true` to *add* the incoming records rather than the\r
730 * default which is to have the incoming records *replace* the existing stoire contents.\r
731 * \r
732 * @return {Ext.data.Store} this\r
733 * @since 1.1.0\r
734 */\r
735 load: function(options) {\r
736 var me = this;\r
737\r
738 // Legacy option. Specifying a function was allowed.\r
739 if (typeof options === 'function') {\r
740 options = {\r
741 callback: options\r
742 };\r
743 } else {\r
744 // We may mutate the options object in setLoadOptions.\r
745 options = options ? Ext.Object.chain(options) : {};\r
746 }\r
747\r
748 me.pendingLoadOptions = options;\r
749\r
750 // If we are configured to load asynchronously (the default for async proxies)\r
751 // then schedule a flush, unless one is already scheduled.\r
752 if (me.getAsynchronousLoad()) {\r
753 if (!me.loadTimer) {\r
754 me.loadTimer = Ext.asap(me.flushLoad, me);\r
755 }\r
756 }\r
757 // If we are configured to load synchronously (the default for sync proxies)\r
758 // then flush the load now.\r
759 else {\r
760 me.flushLoad();\r
761 }\r
762 return me;\r
763 },\r
764\r
765 /**\r
766 * Called when the event handler which called the {@link #method-load} method exits.\r
767 */\r
768 flushLoad: function() {\r
769 var me = this,\r
770 options = me.pendingLoadOptions,\r
771 operation;\r
772\r
773 // If it gets called programatically before the timer fired, the listener will need cancelling.\r
774 me.clearLoadTask();\r
775 if (!options) {\r
776 return;\r
777 }\r
778\r
779 me.setLoadOptions(options);\r
780\r
781 if (me.getRemoteSort() && options.sorters) {\r
782 me.fireEvent('beforesort', me, options.sorters);\r
783 }\r
784 \r
785 operation = Ext.apply({\r
786 internalScope: me,\r
787 internalCallback: me.onProxyLoad,\r
788 scope: me\r
789 }, options);\r
790 me.lastOptions = operation;\r
791 \r
792\r
793 operation = me.createOperation('read', operation);\r
794\r
795 if (me.fireEvent('beforeload', me, operation) !== false) {\r
796 me.onBeforeLoad(operation);\r
797 me.loading = true;\r
798 operation.execute();\r
799 }\r
800 },\r
801\r
802 /**\r
803 * Reloads the store using the last options passed to the {@link #method-load} method. You can use the reload method to reload the\r
804 * store using the parameters from the last load() call. For example:\r
805 *\r
806 * store.load({\r
807 * params : {\r
808 * userid : 22216\r
809 * }\r
810 * });\r
811 *\r
812 * //...\r
813 *\r
814 * store.reload();\r
815 *\r
816 * The initial {@link #method-load} execution will pass the `userid` parameter in the request. The {@link #reload} execution\r
817 * will also send the same `userid` parameter in its request as it will reuse the `params` object from the last {@link #method-load} call.\r
818 *\r
819 * You can override a param by passing in the config object with the `params` object:\r
820 *\r
821 * store.load({\r
822 * params : {\r
823 * userid : 22216,\r
824 * foo : 'bar'\r
825 * }\r
826 * });\r
827 *\r
828 * //...\r
829 *\r
830 * store.reload({\r
831 * params : {\r
832 * userid : 1234\r
833 * }\r
834 * });\r
835 *\r
836 * The initial {@link #method-load} execution sends the `userid` and `foo` parameters but in the {@link #reload} it only sends\r
837 * the `userid` paramter because you are overriding the `params` config not just overriding the one param. To only change a single param\r
838 * but keep other params, you will have to get the last params from the {@link #lastOptions} property:\r
839 *\r
840 * var lastOptions = store.lastOptions,\r
841 * lastParams = Ext.clone(lastOptions.params); // make a copy of the last params so we don't affect future reload() calls\r
842 *\r
843 * lastParams.userid = 1234;\r
844 *\r
845 * store.reload({\r
846 * params : lastParams\r
847 * });\r
848 *\r
849 * This will now send the `userid` parameter as `1234` and the `foo` param as `'bar'`.\r
850 *\r
851 * @param {Object} [options] A config object which contains options which may override the options passed to the previous load call. See the\r
852 * {@link #method-load} method for valid configs.\r
853 */\r
854 reload: function(options) {\r
855 var o = Ext.apply({}, options, this.lastOptions);\r
856 return this.load(o);\r
857 },\r
858\r
859 onEndUpdate: function() {\r
860 var me = this;\r
861\r
862 if (me.needsSync && me.autoSync && !me.autoSyncSuspended) {\r
863 me.sync();\r
864 }\r
865 },\r
866\r
867 /**\r
868 * @private\r
869 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..\r
870 * @param {Ext.data.Model} record The model instance that was edited\r
871 * @since 3.4.0\r
872 */\r
873 afterReject: function(record) {\r
874 var me = this;\r
875 // Must pass the 5th param (modifiedFieldNames) as null, otherwise the\r
876 // event firing machinery appends the listeners "options" object to the arg list\r
877 // which may get used as the modified fields array by a handler.\r
878 // This array is used for selective grid cell updating by Grid View.\r
879 // Null will be treated as though all cells need updating.\r
880 if (me.contains(record)) {\r
881 me.onUpdate(record, Ext.data.Model.REJECT, null);\r
882 me.fireEvent('update', me, record, Ext.data.Model.REJECT, null);\r
883 }\r
884 },\r
885\r
886 /**\r
887 * @private\r
888 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.\r
889 * @param {Ext.data.Model} record The model instance that was edited\r
890 * @since 3.4.0\r
891 */\r
892 afterCommit: function(record, modifiedFieldNames) {\r
893 var me = this;\r
894 if (!modifiedFieldNames) {\r
895 modifiedFieldNames = null;\r
896 }\r
897 if (me.contains(record)) {\r
898 me.onUpdate(record, Ext.data.Model.COMMIT, modifiedFieldNames);\r
899 me.fireEvent('update', me, record, Ext.data.Model.COMMIT, modifiedFieldNames);\r
900 }\r
901 },\r
902 \r
903 afterErase: function(record) {\r
904 this.onErase(record);\r
905 },\r
906 \r
907 onErase: Ext.emptyFn,\r
908\r
909 onUpdate: Ext.emptyFn,\r
910\r
911 /**\r
912 * @private\r
913 */\r
914 onDestroy: function() {\r
915 var me = this,\r
916 proxy = me.getProxy();\r
917\r
918 me.clearLoadTask();\r
919 me.getData().destroy();\r
920 me.data = null;\r
921 me.setProxy(null);\r
922 \r
923 if (proxy.autoCreated) {\r
924 proxy.destroy();\r
925 }\r
926 \r
927 me.setModel(null);\r
928 },\r
929\r
930 \r
931 /**\r
932 * Returns true if the store has a pending load task.\r
933 * @return {Boolean} `true` if the store has a pending load task.\r
934 * @private\r
935 */\r
936 hasPendingLoad: function() {\r
937 return !!this.pendingLoadOptions || this.isLoading();\r
938 },\r
939\r
940 /**\r
941 * Returns true if the Store is currently performing a load operation\r
942 * @return {Boolean} `true` if the Store is currently loading\r
943 */\r
944 isLoading: function() {\r
945 return !!this.loading;\r
946 },\r
947\r
948 /**\r
949 * Returns `true` if the Store has been loaded.\r
950 * @return {Boolean} `true` if the Store has been loaded.\r
951 */\r
952 isLoaded: function() {\r
953 return this.loadCount > 0;\r
954 },\r
955\r
956 /**\r
957 * Suspends automatically syncing the Store with its Proxy. Only applicable if {@link #autoSync} is `true`\r
958 */\r
959 suspendAutoSync: function() {\r
960 ++this.autoSyncSuspended;\r
961 },\r
962\r
963 /**\r
964 * Resumes automatically syncing the Store with its Proxy. Only applicable if {@link #autoSync} is `true`\r
965 * @param {Boolean} syncNow Pass `true` to synchronize now. Only synchronizes with the Proxy if the suspension\r
966 * count has gone to zero (We are not under a higher level of suspension)\r
967 * \r
968 */\r
969 resumeAutoSync: function(syncNow) {\r
970 var me = this;\r
971\r
972 //<debug>\r
973 if (!me.autoSyncSuspended) {\r
974 Ext.log.warn('Mismatched call to resumeAutoSync - auto synchronization is currently not suspended.');\r
975 }\r
976 //</debug>\r
977 if (me.autoSyncSuspended && ! --me.autoSyncSuspended) {\r
978 if (syncNow) {\r
979 me.sync();\r
980 }\r
981 }\r
982 },\r
983\r
984 /**\r
985 * Removes all records from the store. This method does a "fast remove",\r
986 * individual remove events are not called. The {@link #clear} event is\r
987 * fired upon completion.\r
988 * @method\r
989 * @since 1.1.0\r
990 */\r
991 removeAll: Ext.emptyFn,\r
992 // individual store subclasses should implement a "fast" remove\r
993 // and fire a clear event afterwards\r
994 \r
995 // to be implemented by subclasses\r
996 clearData: Ext.emptyFn,\r
997\r
998 privates: {\r
999 /**\r
1000 * @private\r
1001 * Returns the array of records which have been removed since the last time this store was synced.\r
1002 *\r
1003 * This is used internally, when purging removed records after a successful sync.\r
1004 * This is overridden by TreeStore because TreeStore accumulates deleted records on removal\r
1005 * of child nodes from their parent, *not* on removal of records from its collection. The collection\r
1006 * has records added on expand, and removed on collapse.\r
1007 */\r
1008 getRawRemovedRecords: function() {\r
1009 return this.removed;\r
1010 },\r
1011\r
1012 onExtraParamsChanged: function() {\r
1013 \r
1014 },\r
1015\r
1016 clearLoadTask: function() {\r
1017 Ext.asapCancel(this.loadTimer);\r
1018 this.pendingLoadOptions = this.loadTimer = null;\r
1019 },\r
1020\r
1021 cleanRemoved: function() {\r
1022 // Must use class-specific getRawRemovedRecords.\r
1023 // Regular Stores add to the "removed" property on remove.\r
1024 // TreeStores are having records removed all the time; node collapse removes.\r
1025 // TreeStores add to the "removedNodes" property onNodeRemove\r
1026 var removed = this.getRawRemovedRecords(),\r
1027 len, i;\r
1028\r
1029 if (removed) {\r
1030 for (i = 0, len = removed.length; i < len; ++i) {\r
1031 removed[i].unjoin(this);\r
1032 }\r
1033 removed.length = 0;\r
1034 }\r
1035 },\r
1036\r
1037 createOperation: function(type, options) {\r
1038 var me = this,\r
1039 proxy = me.getProxy(),\r
1040 listeners;\r
1041\r
1042 if (!me.proxyListeners) {\r
1043 listeners = {\r
1044 scope: me,\r
1045 destroyable: true,\r
1046 beginprocessresponse: me.beginUpdate,\r
1047 endprocessresponse: me.endUpdate\r
1048 };\r
1049\r
1050 if (!me.disableMetaChangeEvent) {\r
1051 listeners.metachange = me.onMetaChange;\r
1052 }\r
1053 me.proxyListeners = proxy.on(listeners);\r
1054 }\r
1055 return proxy.createOperation(type, options);\r
1056 },\r
1057\r
1058 createImplicitModel: function(fields) {\r
1059 var me = this,\r
1060 modelCfg = {\r
1061 extend: me.implicitModel,\r
1062 statics: {\r
1063 defaultProxy: 'memory'\r
1064 }\r
1065 },\r
1066 proxy, model;\r
1067\r
1068 if (fields) {\r
1069 modelCfg.fields = fields;\r
1070 }\r
1071 model = Ext.define(null, modelCfg);\r
1072\r
1073 me.setModel(model);\r
1074\r
1075 proxy = me.getProxy();\r
1076 if (proxy) {\r
1077 model.setProxy(proxy);\r
1078 } else {\r
1079 me.setProxy(model.getProxy());\r
1080 }\r
1081 },\r
1082\r
1083 loadsSynchronously: function() {\r
1084 return this.getProxy().isSynchronous;\r
1085 },\r
1086\r
1087 onBeforeLoad: Ext.privateFn,\r
1088\r
1089 removeFromRemoved: function(record) {\r
1090 // Must use class-specific getRawRemovedRecords.\r
1091 // Regular Stores add to the "removed" property on remove.\r
1092 // TreeStores are having records removed all the time; node collapse removes.\r
1093 // TreeStores add to the "removedNodes" property onNodeRemove\r
1094 var removed = this.getRawRemovedRecords();\r
1095 if (removed) {\r
1096 Ext.Array.remove(removed, record);\r
1097 record.unjoin(this);\r
1098 }\r
1099 },\r
1100\r
1101 setLoadOptions: function(options) {\r
1102 var me = this,\r
1103 filters, sorters;\r
1104\r
1105 if (me.getRemoteFilter()) {\r
1106 filters = me.getFilters(false);\r
1107 if (filters && filters.getCount()) {\r
1108 options.filters = filters.getRange();\r
1109 }\r
1110 }\r
1111\r
1112 if (me.getRemoteSort()) {\r
1113 sorters = me.getSorters(false);\r
1114 if (sorters && sorters.getCount()) {\r
1115 options.sorters = sorters.getRange();\r
1116 }\r
1117 }\r
1118 }\r
1119 }\r
1120\r
1121});\r