3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
17 * @class Ext.data.AbstractStore
19 * <p>AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
20 * but offers a set of methods used by both of those subclasses.</p>
22 * <p>We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
23 * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
24 * AbstractStore is and is not.</p>
26 * <p>AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be
27 * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
28 * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.</p>
30 * <p>AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
31 * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
32 * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data -
33 * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
34 * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.</p>
36 * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
37 * or can be completed on the server. This is controlled by the {@link #remoteSort} and (@link #remoteFilter{ config
38 * options. For more information see the {@link #sort} and {@link #filter} methods.
40 Ext.define('Ext.data.AbstractStore', {
41 requires: ['Ext.util.MixedCollection', 'Ext.data.Operation', 'Ext.util.Filter'],
44 observable: 'Ext.util.Observable',
45 sortable: 'Ext.util.Sortable'
49 create: function(store){
54 store = Ext.createByAlias('store.' + store.type, store);
64 * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
65 * object or a Proxy instance - see {@link #setProxy} for details.
69 * @cfg {Boolean/Object} autoLoad If data is not specified, and if autoLoad is true or an Object, this store's load method
70 * is automatically called after creation. If the value of autoLoad is an Object, this Object will be passed to the store's
71 * load method. Defaults to false.
76 * @cfg {Boolean} autoSync True to automatically sync the Store with its Proxy after every edit to one of its Records.
82 * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
83 * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
84 * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
85 * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
86 * @property batchUpdateMode
89 batchUpdateMode: 'operation',
92 * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
93 * Defaults to true, ignored if {@link #remoteFilter} is true
94 * @property filterOnLoad
100 * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
101 * Defaults to true, igored if {@link #remoteSort} is true
102 * @property sortOnLoad
108 * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's constructor
109 * instead of a model constructor or name.
110 * @property implicitModel
114 implicitModel: false,
117 * The string type of the Proxy to create if none is specified. This defaults to creating a {@link Ext.data.proxy.Memory memory proxy}.
118 * @property defaultProxyType
121 defaultProxyType: 'memory',
124 * True if the Store has already been destroyed via {@link #destroyStore}. If this is true, the reference to Store should be deleted
125 * as it will not function correctly any more.
126 * @property isDestroyed
134 * @cfg {String} storeId Optional unique identifier for this store. If present, this Store will be registered with
135 * the {@link Ext.data.StoreManager}, making it easy to reuse elsewhere. Defaults to undefined.
139 * @cfg {Array} fields
140 * This may be used in place of specifying a {@link #model} configuration. The fields should be a
141 * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
142 * with these fields. In general this configuration option should be avoided, it exists for the purposes of
143 * backwards compatibility. For anything more complicated, such as specifying a particular id property or
144 * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model} config.
150 constructor: function(config) {
157 * Fired when a Model instance has been added to this Store
158 * @param {Ext.data.Store} store The store
159 * @param {Array} records The Model instances that were added
160 * @param {Number} index The index at which the instances were inserted
166 * Fired when a Model instance has been removed from this Store
167 * @param {Ext.data.Store} store The Store object
168 * @param {Ext.data.Model} record The record that was removed
169 * @param {Number} index The index of the record that was removed
175 * Fires when a Record has been updated
176 * @param {Store} this
177 * @param {Ext.data.Model} record The Model instance that was updated
178 * @param {String} operation The update operation being performed. Value may be one of:
181 Ext.data.Model.REJECT
182 Ext.data.Model.COMMIT
189 * Fires whenever the records in the Store have changed in some way - this could include adding or removing records,
190 * or updating the data in existing records
191 * @param {Ext.data.Store} this The data store
198 * @param {Ext.data.Store} store This Store
199 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store
205 * Fires whenever the store reads data from a remote data source.
206 * @param {Ext.data.Store} this
207 * @param {Array} records An array of records
208 * @param {Boolean} successful True if the operation was successful.
214 * Called before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
215 * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
220 * Fired after the {@link #removeAll} method is called.
221 * @param {Ext.data.Store} this
226 Ext.apply(me, config);
227 // don't use *config* anymore from here on... use *me* instead...
230 * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
231 * at which point this is cleared.
238 me.mixins.observable.constructor.apply(me, arguments);
239 me.model = Ext.ModelManager.getModel(me.model);
242 * @property modelDefaults
245 * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
246 * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
247 * for examples. This should not need to be used by application developers.
253 //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
254 if (!me.model && me.fields) {
255 me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
256 extend: 'Ext.data.Model',
258 proxy: me.proxy || me.defaultProxyType
263 me.implicitModel = true;
266 //ensures that the Proxy is instantiated correctly
267 me.setProxy(me.proxy || me.model.getProxy());
269 if (me.id && !me.storeId) {
275 Ext.data.StoreManager.register(me);
278 me.mixins.sortable.initSortable.call(me);
281 * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
283 * @type Ext.util.MixedCollection
285 filters = me.decodeFilters(me.filters);
286 me.filters = Ext.create('Ext.util.MixedCollection');
287 me.filters.addAll(filters);
291 * Sets the Store's Proxy by string, config object or Proxy instance
292 * @param {String|Object|Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
293 * or an Ext.data.proxy.Proxy instance
294 * @return {Ext.data.proxy.Proxy} The attached Proxy object
296 setProxy: function(proxy) {
299 if (proxy instanceof Ext.data.proxy.Proxy) {
300 proxy.setModel(me.model);
302 if (Ext.isString(proxy)) {
311 proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
320 * Returns the proxy currently attached to this proxy instance
321 * @return {Ext.data.proxy.Proxy} The Proxy instance
323 getProxy: function() {
327 //saves any phantom records
328 create: function(data, options) {
330 instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
333 options = options || {};
335 Ext.applyIf(options, {
340 operation = Ext.create('Ext.data.Operation', options);
342 me.proxy.create(operation, me.onProxyWrite, me);
348 return this.load.apply(this, arguments);
351 onProxyRead: Ext.emptyFn,
353 update: function(options) {
356 options = options || {};
358 Ext.applyIf(options, {
360 records: me.getUpdatedRecords()
363 operation = Ext.create('Ext.data.Operation', options);
365 return me.proxy.update(operation, me.onProxyWrite, me);
370 * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
371 * the updates provided by the Proxy
373 onProxyWrite: function(operation) {
375 success = operation.wasSuccessful(),
376 records = operation.getRecords();
378 switch (operation.action) {
380 me.onCreateRecords(records, operation, success);
383 me.onUpdateRecords(records, operation, success);
386 me.onDestroyRecords(records, operation, success);
391 me.fireEvent('write', me, operation);
392 me.fireEvent('datachanged', me);
394 //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
395 Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
399 //tells the attached proxy to destroy the given records
400 destroy: function(options) {
404 options = options || {};
406 Ext.applyIf(options, {
408 records: me.getRemovedRecords()
411 operation = Ext.create('Ext.data.Operation', options);
413 return me.proxy.destroy(operation, me.onProxyWrite, me);
418 * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
421 onBatchOperationComplete: function(batch, operation) {
422 return this.onProxyWrite(operation);
427 * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
428 * and updates the Store's internal data MixedCollection.
430 onBatchComplete: function(batch, operation) {
432 operations = batch.operations,
433 length = operations.length,
438 for (i = 0; i < length; i++) {
439 me.onProxyWrite(operations[i]);
444 me.fireEvent('datachanged', me);
447 onBatchException: function(batch, operation) {
448 // //decide what to do... could continue with the next operation
451 // //or retry the last operation
457 * Filter function for new records.
459 filterNew: function(item) {
460 // only want phantom records that are valid
461 return item.phantom === true && item.isValid();
465 * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
466 * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
467 * @return {Array} The Model instances
469 getNewRecords: function() {
474 * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
475 * @return {Array} The updated Model instances
477 getUpdatedRecords: function() {
483 * Filter function for updated records.
485 filterUpdated: function(item) {
486 // only want dirty records, not phantoms that are valid
487 return item.dirty === true && item.phantom !== true && item.isValid();
491 * Returns any records that have been removed from the store but not yet destroyed on the proxy.
492 * @return {Array} The removed Model instances
494 getRemovedRecords: function() {
498 filter: function(filters, value) {
504 * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
505 * @param {Array} filters The filters array
506 * @return {Array} Array of Ext.util.Filter objects
508 decodeFilters: function(filters) {
509 if (!Ext.isArray(filters)) {
510 if (filters === undefined) {
517 var length = filters.length,
518 Filter = Ext.util.Filter,
521 for (i = 0; i < length; i++) {
524 if (!(config instanceof Filter)) {
529 //support for 3.x style filters where a function can be defined as 'fn'
531 config.filterFn = config.fn;
534 //support a function to be passed as a filter definition
535 if (typeof config == 'function') {
541 filters[i] = new Filter(config);
548 clearFilter: function(supressEvent) {
552 isFiltered: function() {
556 filterBy: function(fn, scope) {
561 * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
562 * and deleted records in the store, updating the Store's internal representation of the records
563 * as each operation completes.
568 toCreate = me.getNewRecords(),
569 toUpdate = me.getUpdatedRecords(),
570 toDestroy = me.getRemovedRecords(),
573 if (toCreate.length > 0) {
574 options.create = toCreate;
578 if (toUpdate.length > 0) {
579 options.update = toUpdate;
583 if (toDestroy.length > 0) {
584 options.destroy = toDestroy;
588 if (needsSync && me.fireEvent('beforesync', options) !== false) {
589 me.proxy.batch(options, me.getBatchListeners());
596 * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
597 * This is broken out into a separate function to allow for customisation of the listeners
598 * @return {Object} The listeners object
600 getBatchListeners: function() {
604 exception: me.onBatchException
607 if (me.batchUpdateMode == 'operation') {
608 listeners.operationcomplete = me.onBatchOperationComplete;
610 listeners.complete = me.onBatchComplete;
616 //deprecated, will be removed in 5.0
618 return this.sync.apply(this, arguments);
622 * Loads the Store using its configured {@link #proxy}.
623 * @param {Object} options Optional config object. This is passed into the {@link Ext.data.Operation Operation}
624 * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
626 load: function(options) {
630 options = options || {};
632 Ext.applyIf(options, {
634 filters: me.filters.items,
635 sorters: me.getSorters()
638 operation = Ext.create('Ext.data.Operation', options);
640 if (me.fireEvent('beforeload', me, operation) !== false) {
642 me.proxy.read(operation, me.onProxyLoad, me);
650 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
651 * @param {Ext.data.Model} record The model instance that was edited
653 afterEdit : function(record) {
660 me.fireEvent('update', me, record, Ext.data.Model.EDIT);
665 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
666 * @param {Ext.data.Model} record The model instance that was edited
668 afterReject : function(record) {
669 this.fireEvent('update', this, record, Ext.data.Model.REJECT);
674 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
675 * @param {Ext.data.Model} record The model instance that was edited
677 afterCommit : function(record) {
678 this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
681 clearData: Ext.emptyFn,
683 destroyStore: function() {
686 if (!me.isDestroyed) {
688 Ext.data.StoreManager.unregister(me);
693 // Ext.destroy(this.proxy);
694 me.reader = me.writer = null;
696 me.isDestroyed = true;
698 if (me.implicitModel) {
699 Ext.destroy(me.model);
704 doSort: function(sorterFn) {
707 //the load function will pick up the new sorters and request the sorted data from the proxy
710 me.data.sortBy(sorterFn);
711 me.fireEvent('datachanged', me);
715 getCount: Ext.emptyFn,
717 getById: Ext.emptyFn,
720 * Removes all records from the store. This method does a "fast remove",
721 * individual remove events are not called. The {@link #clear} event is
722 * fired upon completion.
725 removeAll: Ext.emptyFn,
726 // individual substores should implement a "fast" remove
727 // and fire a clear event afterwards
730 * Returns true if the Store is currently performing a load operation
731 * @return {Boolean} True if the Store is currently loading
733 isLoading: function() {