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.
18 * AbstractStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.TreeStore}. It's never used directly,
19 * but offers a set of methods used by both of those subclasses.
21 * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
22 * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
23 * AbstractStore is and is not.
25 * AbstractStore provides the basic configuration for anything that can be considered a Store. It expects to be
26 * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
27 * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
29 * AbstractStore provides a few helpful methods such as {@link #load} and {@link #sync}, which load and save data
30 * respectively, passing the requests through the configured {@link #proxy}. Both built-in Store subclasses add extra
31 * behavior to each of these functions. Note also that each AbstractStore subclass has its own way of storing data -
32 * in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.MixedCollection MixedCollection}, whereas in
33 * {@link Ext.data.TreeStore TreeStore} we use a {@link Ext.data.Tree} to maintain the data's hierarchy.
35 * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
36 * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
37 * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #sort} and
38 * {@link Ext.data.Store#filter 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
65 * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
66 * see {@link #setProxy} for details.
70 * @cfg {Boolean/Object} autoLoad
71 * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
72 * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
78 * @cfg {Boolean} autoSync
79 * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
84 * @property {String} batchUpdateMode
85 * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
86 * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
87 * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
88 * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
90 batchUpdateMode: 'operation',
93 * @property {Boolean} filterOnLoad
94 * If true, any filters attached to this Store will be run after loading data, before the datachanged event is fired.
95 * Defaults to true, ignored if {@link Ext.data.Store#remoteFilter remoteFilter} is true
100 * @property {Boolean} sortOnLoad
101 * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
102 * Defaults to true, igored if {@link Ext.data.Store#remoteSort remoteSort} is true
107 * @property {Boolean} implicitModel
108 * True if a model was created implicitly for this Store. This happens if a fields array is passed to the Store's
109 * constructor instead of a model constructor or name.
112 implicitModel: false,
115 * @property {String} defaultProxyType
116 * The string type of the Proxy to create if none is specified. This defaults to creating a
117 * {@link Ext.data.proxy.Memory memory proxy}.
119 defaultProxyType: 'memory',
122 * @property {Boolean} isDestroyed
123 * True if the Store has already been destroyed. If this is true, the reference to Store should be deleted
124 * as it will not function correctly any more.
131 * @cfg {String} storeId
132 * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
133 * making it easy to reuse elsewhere. Defaults to undefined.
137 * @cfg {Object[]} fields
138 * This may be used in place of specifying a {@link #model} configuration. The fields should be a
139 * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
140 * with these fields. In general this configuration option should be avoided, it exists for the purposes of
141 * backwards compatibility. For anything more complicated, such as specifying a particular id property or
142 * assocations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
147 * @cfg {String} model
148 * Name of the {@link Ext.data.Model Model} associated with this store.
149 * The string is used as an argument for {@link Ext.ModelManager#getModel}.
155 constructor: function(config) {
162 * Fired when a Model instance has been added to this Store
163 * @param {Ext.data.Store} store The store
164 * @param {Ext.data.Model[]} records The Model instances that were added
165 * @param {Number} index The index at which the instances were inserted
171 * Fired when a Model instance has been removed from this Store
172 * @param {Ext.data.Store} store The Store object
173 * @param {Ext.data.Model} record The record that was removed
174 * @param {Number} index The index of the record that was removed
180 * Fires when a Model instance has been updated
181 * @param {Ext.data.Store} this
182 * @param {Ext.data.Model} record The Model instance that was updated
183 * @param {String} operation The update operation being performed. Value may be one of:
185 * Ext.data.Model.EDIT
186 * Ext.data.Model.REJECT
187 * Ext.data.Model.COMMIT
193 * Fires whenever the records in the Store have changed in some way - this could include adding or removing
194 * records, or updating the data in existing records
195 * @param {Ext.data.Store} this The data store
201 * Fires before a request is made for a new data object. If the beforeload handler returns false the load
202 * action will be canceled.
203 * @param {Ext.data.Store} store This Store
204 * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
211 * Fires whenever the store reads data from a remote data source.
212 * @param {Ext.data.Store} this
213 * @param {Ext.data.Model[]} records An array of records
214 * @param {Boolean} successful True if the operation was successful.
220 * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
221 * @param {Ext.data.Store} store This Store
222 * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
229 * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the synv
230 * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
235 * Fired after the {@link #removeAll} method is called.
236 * @param {Ext.data.Store} this
241 Ext.apply(me, config);
242 // don't use *config* anymore from here on... use *me* instead...
245 * Temporary cache in which removed model instances are kept until successfully synchronised with a Proxy,
246 * at which point this is cleared.
248 * @property {Ext.data.Model[]} removed
252 me.mixins.observable.constructor.apply(me, arguments);
253 me.model = Ext.ModelManager.getModel(me.model);
256 * @property {Object} modelDefaults
258 * A set of default values to be applied to every model instance added via {@link #insert} or created via {@link #create}.
259 * This is used internally by associations to set foreign keys and other fields. See the Association classes source code
260 * for examples. This should not need to be used by application developers.
266 //Supports the 3.x style of simply passing an array of fields to the store, implicitly creating a model
267 if (!me.model && me.fields) {
268 me.model = Ext.define('Ext.data.Store.ImplicitModel-' + (me.storeId || Ext.id()), {
269 extend: 'Ext.data.Model',
271 proxy: me.proxy || me.defaultProxyType
276 me.implicitModel = true;
281 if (Ext.isDefined(Ext.global.console)) {
282 Ext.global.console.warn('Store defined with no model. You may have mistyped the model name.');
287 //ensures that the Proxy is instantiated correctly
288 me.setProxy(me.proxy || me.model.getProxy());
290 if (me.id && !me.storeId) {
296 Ext.data.StoreManager.register(me);
299 me.mixins.sortable.initSortable.call(me);
302 * @property {Ext.util.MixedCollection} filters
303 * The collection of {@link Ext.util.Filter Filters} currently applied to this Store
305 filters = me.decodeFilters(me.filters);
306 me.filters = Ext.create('Ext.util.MixedCollection');
307 me.filters.addAll(filters);
311 * Sets the Store's Proxy by string, config object or Proxy instance
312 * @param {String/Object/Ext.data.proxy.Proxy} proxy The new Proxy, which can be either a type string, a configuration object
313 * or an Ext.data.proxy.Proxy instance
314 * @return {Ext.data.proxy.Proxy} The attached Proxy object
316 setProxy: function(proxy) {
319 if (proxy instanceof Ext.data.proxy.Proxy) {
320 proxy.setModel(me.model);
322 if (Ext.isString(proxy)) {
331 proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
340 * Returns the proxy currently attached to this proxy instance
341 * @return {Ext.data.proxy.Proxy} The Proxy instance
343 getProxy: function() {
347 //saves any phantom records
348 create: function(data, options) {
350 instance = Ext.ModelManager.create(Ext.applyIf(data, me.modelDefaults), me.model.modelName),
353 options = options || {};
355 Ext.applyIf(options, {
360 operation = Ext.create('Ext.data.Operation', options);
362 me.proxy.create(operation, me.onProxyWrite, me);
368 return this.load.apply(this, arguments);
371 onProxyRead: Ext.emptyFn,
373 update: function(options) {
376 options = options || {};
378 Ext.applyIf(options, {
380 records: me.getUpdatedRecords()
383 operation = Ext.create('Ext.data.Operation', options);
385 return me.proxy.update(operation, me.onProxyWrite, me);
390 * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
391 * the updates provided by the Proxy
393 onProxyWrite: function(operation) {
395 success = operation.wasSuccessful(),
396 records = operation.getRecords();
398 switch (operation.action) {
400 me.onCreateRecords(records, operation, success);
403 me.onUpdateRecords(records, operation, success);
406 me.onDestroyRecords(records, operation, success);
411 me.fireEvent('write', me, operation);
412 me.fireEvent('datachanged', me);
414 //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
415 Ext.callback(operation.callback, operation.scope || me, [records, operation, success]);
419 //tells the attached proxy to destroy the given records
420 destroy: function(options) {
424 options = options || {};
426 Ext.applyIf(options, {
428 records: me.getRemovedRecords()
431 operation = Ext.create('Ext.data.Operation', options);
433 return me.proxy.destroy(operation, me.onProxyWrite, me);
438 * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
441 onBatchOperationComplete: function(batch, operation) {
442 return this.onProxyWrite(operation);
447 * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
448 * and updates the Store's internal data MixedCollection.
450 onBatchComplete: function(batch, operation) {
452 operations = batch.operations,
453 length = operations.length,
458 for (i = 0; i < length; i++) {
459 me.onProxyWrite(operations[i]);
464 me.fireEvent('datachanged', me);
467 onBatchException: function(batch, operation) {
468 // //decide what to do... could continue with the next operation
471 // //or retry the last operation
477 * Filter function for new records.
479 filterNew: function(item) {
480 // only want phantom records that are valid
481 return item.phantom === true && item.isValid();
485 * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
486 * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one)
487 * @return {Ext.data.Model[]} The Model instances
489 getNewRecords: function() {
494 * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy
495 * @return {Ext.data.Model[]} The updated Model instances
497 getUpdatedRecords: function() {
503 * Filter function for updated records.
505 filterUpdated: function(item) {
506 // only want dirty records, not phantoms that are valid
507 return item.dirty === true && item.phantom !== true && item.isValid();
511 * Returns any records that have been removed from the store but not yet destroyed on the proxy.
512 * @return {Ext.data.Model[]} The removed Model instances
514 getRemovedRecords: function() {
518 filter: function(filters, value) {
524 * Normalizes an array of filter objects, ensuring that they are all Ext.util.Filter instances
525 * @param {Object[]} filters The filters array
526 * @return {Ext.util.Filter[]} Array of Ext.util.Filter objects
528 decodeFilters: function(filters) {
529 if (!Ext.isArray(filters)) {
530 if (filters === undefined) {
537 var length = filters.length,
538 Filter = Ext.util.Filter,
541 for (i = 0; i < length; i++) {
544 if (!(config instanceof Filter)) {
549 //support for 3.x style filters where a function can be defined as 'fn'
551 config.filterFn = config.fn;
554 //support a function to be passed as a filter definition
555 if (typeof config == 'function') {
561 filters[i] = new Filter(config);
568 clearFilter: function(supressEvent) {
572 isFiltered: function() {
576 filterBy: function(fn, scope) {
581 * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
582 * and deleted records in the store, updating the Store's internal representation of the records
583 * as each operation completes.
588 toCreate = me.getNewRecords(),
589 toUpdate = me.getUpdatedRecords(),
590 toDestroy = me.getRemovedRecords(),
593 if (toCreate.length > 0) {
594 options.create = toCreate;
598 if (toUpdate.length > 0) {
599 options.update = toUpdate;
603 if (toDestroy.length > 0) {
604 options.destroy = toDestroy;
608 if (needsSync && me.fireEvent('beforesync', options) !== false) {
609 me.proxy.batch(options, me.getBatchListeners());
616 * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
617 * This is broken out into a separate function to allow for customisation of the listeners
618 * @return {Object} The listeners object
620 getBatchListeners: function() {
624 exception: me.onBatchException
627 if (me.batchUpdateMode == 'operation') {
628 listeners.operationcomplete = me.onBatchOperationComplete;
630 listeners.complete = me.onBatchComplete;
636 //deprecated, will be removed in 5.0
638 return this.sync.apply(this, arguments);
642 * Loads the Store using its configured {@link #proxy}.
643 * @param {Object} options (optional) config object. This is passed into the {@link Ext.data.Operation Operation}
644 * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function
646 load: function(options) {
650 options = options || {};
652 Ext.applyIf(options, {
654 filters: me.filters.items,
655 sorters: me.getSorters()
658 operation = Ext.create('Ext.data.Operation', options);
660 if (me.fireEvent('beforeload', me, operation) !== false) {
662 me.proxy.read(operation, me.onProxyLoad, me);
670 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
671 * @param {Ext.data.Model} record The model instance that was edited
673 afterEdit : function(record) {
680 me.fireEvent('update', me, record, Ext.data.Model.EDIT);
685 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
686 * @param {Ext.data.Model} record The model instance that was edited
688 afterReject : function(record) {
689 this.fireEvent('update', this, record, Ext.data.Model.REJECT);
694 * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
695 * @param {Ext.data.Model} record The model instance that was edited
697 afterCommit : function(record) {
698 this.fireEvent('update', this, record, Ext.data.Model.COMMIT);
701 clearData: Ext.emptyFn,
703 destroyStore: function() {
706 if (!me.isDestroyed) {
708 Ext.data.StoreManager.unregister(me);
713 // Ext.destroy(this.proxy);
714 me.reader = me.writer = null;
716 me.isDestroyed = true;
718 if (me.implicitModel) {
719 Ext.destroy(me.model);
724 doSort: function(sorterFn) {
727 //the load function will pick up the new sorters and request the sorted data from the proxy
730 me.data.sortBy(sorterFn);
731 me.fireEvent('datachanged', me);
735 getCount: Ext.emptyFn,
737 getById: Ext.emptyFn,
740 * Removes all records from the store. This method does a "fast remove",
741 * individual remove events are not called. The {@link #clear} event is
742 * fired upon completion.
745 removeAll: Ext.emptyFn,
746 // individual substores should implement a "fast" remove
747 // and fire a clear event afterwards
750 * Returns true if the Store is currently performing a load operation
751 * @return {Boolean} True if the Store is currently loading
753 isLoading: function() {
754 return !!this.loading;