text: /**
   * ProxyStore is a superclass of {@link Ext.data.Store} and {@link Ext.data.BufferedStore}. It's never used directly,
   * but offers a set of methods used by both of those subclasses.
   *
   * We've left it here in the docs for reference purposes, but unless you need to make a whole new type of Store, what
   * you're probably looking for is {@link Ext.data.Store}. If you're still interested, here's a brief description of what
   * ProxyStore is and is not.
   *
   * ProxyStore provides the basic configuration for anything that can be considered a Store. It expects to be
   * given a {@link Ext.data.Model Model} that represents the type of data in the Store. It also expects to be given a
   * {@link Ext.data.proxy.Proxy Proxy} that handles the loading of data into the Store.
   *
   * ProxyStore provides a few helpful methods such as {@link #method-load} and {@link #sync}, which load and save data
   * respectively, passing the requests through the configured {@link #proxy}.
   *
   * Built-in Store subclasses add extra behavior to each of these functions. Note also that each ProxyStore subclass
   * has its own way of storing data - in {@link Ext.data.Store} the data is saved as a flat {@link Ext.util.Collection Collection},
   * whereas in {@link Ext.data.BufferedStore BufferedStore} we use a {@link Ext.data.PageMap} to maintain a client side cache of pages of records.
   *
   * The store provides filtering and sorting support. This sorting/filtering can happen on the client side
   * or can be completed on the server. This is controlled by the {@link Ext.data.Store#remoteSort remoteSort} and
   * {@link Ext.data.Store#remoteFilter remoteFilter} config options. For more information see the {@link #method-sort} and
   * {@link Ext.data.Store#filter filter} methods.
   */
  Ext.define('Ext.data.ProxyStore', {
      extend: 'Ext.data.AbstractStore',
  
      requires: [
          'Ext.data.Model',
          'Ext.data.proxy.Proxy',
          'Ext.data.proxy.Memory',
          'Ext.data.operation.*'
      ],
  
      config: {
          // @cmd-auto-dependency {aliasPrefix: "model.", mvc: true, blame: "all"}
          /**
           * @cfg {String/Ext.data.Model} model
           * Name of the {@link Ext.data.Model Model} associated with this store. See
           * {@link Ext.data.Model#entityName}.
           *
           * May also be the actual Model subclass.
           *
           * This config is required for the store to be able to read data unless you have defined
           * the {@link #fields} config which will create an anonymous `Ext.data.Model`.
           */
          model: undefined,
  
          // @cmd-auto-dependency {aliasPrefix: "data.field."}
          /**
           * @cfg {Object[]/String[]} fields
           * @inheritdoc Ext.data.Model#cfg-fields
           * 
           * @localdoc **Note:** In general, this configuration option should only be used 
           * for simple stores like a two-field store of 
           * {@link Ext.form.field.ComboBox ComboBox}. For anything more complicated, such 
           * as specifying a particular id property or associations, a 
           * {@link Ext.data.Model Model} should be defined and specified for the 
           * {@link #model} config.
           * 
           * @since 2.3.0
           */
          fields: null,
  
          // @cmd-auto-dependency {aliasPrefix : "proxy."}
          /**
           * @cfg {String/Ext.data.proxy.Proxy/Object} proxy
           * The Proxy to use for this Store. This can be either a string, a config object or a Proxy instance -
           * see {@link #setProxy} for details.
           * @since 1.1.0
           */
          proxy: undefined,
  
          /**
           * @cfg {Boolean/Object} autoLoad
           * If data is not specified, and if autoLoad is true or an Object, this store's load method is automatically called
           * after creation. If the value of autoLoad is an Object, this Object will be passed to the store's load method.
           *
           * It's important to note that {@link Ext.data.TreeStore Tree Stores} will  
           * load regardless of autoLoad's value if expand is set to true on the 
           * {@link Ext.data.TreeStore#root root node}.
           * 
           * @since 2.3.0
           */
          autoLoad: undefined,
  
          /**
           * @cfg {Boolean} autoSync
           * True to automatically sync the Store with its Proxy after every edit to one of its Records. Defaults to false.
           */
          autoSync: false,
  
          /**
           * @cfg {String} batchUpdateMode
           * Sets the updating behavior based on batch synchronization. 'operation' (the default) will update the Store's
           * internal representation of the data after each operation of the batch has completed, 'complete' will wait until
           * the entire batch has been completed before updating the Store's data. 'complete' is a good choice for local
           * storage proxies, 'operation' is better for remote proxies, where there is a comparatively high latency.
           */
          batchUpdateMode: 'operation',
         
          /**
           * @cfg {Boolean} sortOnLoad
           * If true, any sorters attached to this Store will be run after loading data, before the datachanged event is fired.
           * Defaults to true, ignored if {@link Ext.data.Store#remoteSort remoteSort} is true
           */
          sortOnLoad: true,
  
          /**
           * @cfg {Boolean} [trackRemoved=true]
           * This config controls whether removed records are remembered by this store for
           * later saving to the server.
           */
          trackRemoved: true,
  
          /**
           * @cfg {Boolean} [asynchronousLoad]
           * This defaults to `true` when this store's {@link #cfg-proxy} is asynchronous, such as an
           * {@link Ext.data.proxy.Ajax Ajax proxy}.
           *
           * When the proxy is synchronous, such as a {@link Ext.data.proxy.Memory} memory proxy, this
           * defaults to `false`.
           *
           * *NOTE:* This does not cause synchronous Ajax requests if configured `false` when an Ajax proxy
           * is used. It causes immediate issuing of an Ajax request when {@link #method-load} is called
           * rather than issuing the request at the end of the current event handler run.
           *
           * What this means is that when using an Ajax proxy, calls to 
           * {@link #method-load} do not fire the request to the remote resource 
           * immediately, but schedule a request to be made. This is so that multiple 
           * requests are not fired when mutating a store's remote filters and sorters (as 
           * happens during state restoration). The request is made only once after all 
           * relevant store state is fully set.
           *
           * @since 6.0.1
           */
          asynchronousLoad: undefined
      },
  
      onClassExtended: function(cls, data, hooks) {
          var model = data.model,
              onBeforeClassCreated;
  
          if (typeof model === 'string') {
              onBeforeClassCreated = hooks.onBeforeCreated;
  
              hooks.onBeforeCreated = function() {
                  var me = this,
                      args = arguments;
  
                  Ext.require(model, function() {
                      onBeforeClassCreated.apply(me, args);
                  });
              };
          }
      },
  
      /**
       * @private
       * @property {Boolean}
       * The class name of the model that this store uses if no explicit {@link #model} is given
       */
      implicitModel: 'Ext.data.Model',
  
      /**
       * @property {Object} lastOptions
       * Property to hold the last options from a {@link #method-load} method call. This object is used for the {@link #method-reload}
       * to reuse the same options. Please see {@link #method-reload} for a simple example on how to use the lastOptions property.
       */
  
      /**
       * @property {Number} autoSyncSuspended
       * A counter to track suspensions.
       * @private
       */
      autoSyncSuspended: 0,
  
      //documented above
      constructor: function(config) {
          var me = this;
  
          // <debug>
          var configModel = me.model;
          // </debug>
  
          /**
           * @event beforeload
           * Fires before a request is made for a new data object. If the beforeload handler returns false the load
           * action will be canceled.
           * @param {Ext.data.Store} store This Store
           * @param {Ext.data.operation.Operation} operation The Ext.data.operation.Operation object that will be passed to the Proxy to
           * load the Store
           * @since 1.1.0
           */
  
          /**
           * @event load
           * Fires whenever the store reads data from a remote data source.
           * @param {Ext.data.Store} this
           * @param {Ext.data.Model[]} records An array of records
           * @param {Boolean} successful True if the operation was successful.
           * @param {Ext.data.operation.Read} operation The 
           * {@link Ext.data.operation.Read Operation} object that was used in the data 
           * load call
           * @since 1.1.0
           */
  
          /**
           * @event write
           * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
           * @param {Ext.data.Store} store This Store
           * @param {Ext.data.operation.Operation} operation The {@link Ext.data.operation.Operation Operation} object that was used in
           * the write
           * @since 3.4.0
           */
  
          /**
           * @event beforesync
           * Fired before a call to {@link #sync} is executed. Return false from any listener to cancel the sync
           * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
           */
  
          /**
           * @event metachange
           * Fires when this store's underlying reader (available via the proxy) provides new metadata.
           * Metadata usually consists of new field definitions, but can include any configuration data
           * required by an application, and can be processed as needed in the event handler.
           * This event is currently only fired for JsonReaders.
           * @param {Ext.data.Store} this
           * @param {Object} meta The JSON metadata
           * @since 1.1.0
           */
  
  
          /**
           * Temporary cache in which removed model instances are kept until successfully
           * synchronised with a Proxy, at which point this is cleared.
           *
           * This cache is maintained unless you set `trackRemoved` to `false`.
           *
           * @protected
           * @property {Ext.data.Model[]} removed
           */
          me.removed = [];
  
          me.callParent(arguments);
  
          if (me.getAsynchronousLoad() === false) {
              me.flushLoad();
          }
  
          // <debug>
          if (!me.getModel() && me.useModelWarning !== false && me.getStoreId() !== 'ext-empty-store') {
              // There are a number of ways things could have gone wrong, try to give as much information as possible
              var logMsg = [
                  Ext.getClassName(me) || 'Store',
                  ' created with no model.'
              ];
  
              if (typeof configModel === 'string') {
                  logMsg.push(" The name '", configModel, "'", ' does not correspond to a valid model.');
              }
  
              Ext.log.warn(logMsg.join(''));
          }
          // </debug>
      },
  
      applyAsynchronousLoad: function(asynchronousLoad) {
      // Default in an asynchronousLoad setting.
      // It defaults to false if the proxy is synchronous, and true if the proxy is asynchronous.
          if (asynchronousLoad == null) {
              asynchronousLoad = !this.loadsSynchronously();
          }
          return asynchronousLoad;
      },
  
      updateAutoLoad: function(autoLoad) {
          // Ensure the data collection is set up
          this.getData();
          if (autoLoad) {
              // Defer the load until idle, when the store (and probably the view) is fully constructed
              this.load(Ext.isObject(autoLoad) ? autoLoad : undefined);
          }
      },
  
      /**
       * Returns the total number of {@link Ext.data.Model Model} instances that the {@link Ext.data.proxy.Proxy Proxy}
       * indicates exist. This will usually differ from {@link #getCount} when using paging - getCount returns the
       * number of records loaded into the Store at the moment, getTotalCount returns the number of records that
       * could be loaded into the Store if the Store contained all data
       * @return {Number} The total number of Model instances available via the Proxy. 0 returned if
       * no value has been set via the reader.
       */
      getTotalCount: function() {
          return this.totalCount || 0;
      },
  
      applyFields: function(fields) {
          if (fields) {
              this.createImplicitModel(fields);
          }
      },
  
      applyModel: function(model) {
          if (model) {
              model = Ext.data.schema.Schema.lookupEntity(model);
          } else if (!this.destroying) {
              // If no model, ensure that the fields config is converted to a model.
              this.getFields();
              model = this.getModel() || this.createImplicitModel();
          }
          return model;
      },
  
      applyProxy: function(proxy) {
          var model = this.getModel();
  
          if (proxy !== null) {
              if (proxy) {
                  if (proxy.isProxy) {
                      proxy.setModel(model);
                  } else {
                      if (Ext.isString(proxy)) {
                          proxy = {
                              type: proxy,
                              model: model
                          };
                      } else if (!proxy.model) {
                          proxy = Ext.apply({
                              model: model
                          }, proxy);
                      }
  
                      proxy = Ext.createByAlias('proxy.' + proxy.type, proxy);
                      proxy.autoCreated = true;
                  }
              } else if (model) {
                  proxy = model.getProxy();
                  this.useModelProxy = true;
              }
          
              if (!proxy) {
                  proxy = Ext.createByAlias('proxy.memory');
                  proxy.autoCreated = true;
              }
          }
  
          return proxy;
      },
  
      applyState: function (state) {
          var me = this;
  
          me.callParent([state]);
  
          // This is called during construction. Sorters and filters might have changed
          // which require a reload.
          // If autoLoad is true, it might have loaded synchronously from a memory proxy, so needs to reload.
          // If it is already loaded, we definitely need to reload to apply the state.
          if (me.getAutoLoad() || me.isLoaded()) {
              me.load();
          }
      },
  
      updateProxy: function(proxy, oldProxy) {
          this.proxyListeners = Ext.destroy(this.proxyListeners);
      },
  
      updateTrackRemoved: function (track) {
          this.cleanRemoved();
          this.removed = track ? [] : null;
      },
  
      /**
       * @private
       */
      onMetaChange: function(proxy, meta) {
          this.fireEvent('metachange', this, meta);
      },
  
      //saves any phantom records
      create: function(data, options) {
          var me = this,
              Model = me.getModel(),
              instance = new Model(data),
              operation;
  
          options = Ext.apply({}, options);
          if (!options.records) {
              options.records = [instance];
          }
          options.internalScope = me;
          options.internalCallback = me.onProxyWrite;
  
          operation = me.createOperation('create', options);
          return operation.execute();
      },
  
      read: function() {
          return this.load.apply(this, arguments);
      },
  
      update: function(options) {
          var me = this,
              operation;
              
          options = Ext.apply({}, options);
          if (!options.records) {
              options.records = me.getUpdatedRecords();
          }
          options.internalScope = me;
          options.internalCallback = me.onProxyWrite;
  
          operation = me.createOperation('update', options);
          return operation.execute();
      },
  
      /**
       * @private
       * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
       * the updates provided by the Proxy
       */
      onProxyWrite: function(operation) {
          var me = this,
              success = operation.wasSuccessful(),
              records = operation.getRecords();
  
          switch (operation.getAction()) {
              case 'create':
                  me.onCreateRecords(records, operation, success);
                  break;
              case 'update':
                  me.onUpdateRecords(records, operation, success);
                  break;
              case 'destroy':
                  me.onDestroyRecords(records, operation, success);
                  break;
          }
  
          if (success) {
              me.fireEvent('write', me, operation);
              me.fireEvent('datachanged', me);
          }
      },
      
      // may be implemented by store subclasses
      onCreateRecords: Ext.emptyFn,
      
      // may be implemented by store subclasses
      onUpdateRecords: Ext.emptyFn,
      
      /**
       * Removes any records when a write is returned from the server.
       * @private
       * @param {Ext.data.Model[]} records The array of removed records
       * @param {Ext.data.operation.Operation} operation The operation that just completed
       * @param {Boolean} success True if the operation was successful
       */
      onDestroyRecords: function(records, operation, success) {
          if (success) {
              this.cleanRemoved();
          }
      },
  
      // tells the attached proxy to destroy the given records
      // @since 3.4.0
      erase: function(options) {
          var me = this,
              operation;
  
          options = Ext.apply({}, options);
          if (!options.records) {
              options.records = me.getRemovedRecords();
          }
          options.internalScope = me;
          options.internalCallback = me.onProxyWrite;
  
          operation = me.createOperation('destroy', options);
          return operation.execute();
      },
  
      /**
       * @private
       * Attached as the 'operationcomplete' event listener to a proxy's Batch object. By default just calls through
       * to onProxyWrite.
       */
      onBatchOperationComplete: function(batch, operation) {
          return this.onProxyWrite(operation);
      },
  
      /**
       * @private
       * Attached as the 'complete' event listener to a proxy's Batch object. Iterates over the batch operations
       * and updates the Store's internal data MixedCollection.
       */
      onBatchComplete: function(batch, operation) {
          var me = this,
              operations = batch.operations,
              length = operations.length,
              i;
  
          if (me.batchUpdateMode !== 'operation') {
              me.suspendEvents();
  
              for (i = 0; i < length; i++) {
                  me.onProxyWrite(operations[i]);
              }
  
              me.resumeEvents();
          }
  
          me.isSyncing = false;
          me.fireEvent('datachanged', me);
      },
  
      /**
       * @private
       */
      onBatchException: function(batch, operation) {
          // //decide what to do... could continue with the next operation
          // batch.start();
          //
          // //or retry the last operation
          // batch.retry();
      },
  
      /**
       * @private
       * Filter function for new records.
       */
      filterNew: function(item) {
          // only want phantom records that are valid
          return item.phantom === true && item.isValid();
      },
  
      /**
       * Returns all `{@link Ext.data.Model#property-phantom phantom}` records in this store.
       * @return {Ext.data.Model[]} A possibly empty array of `phantom` records.
       */
      getNewRecords: function() {
          return [];
      },
  
      /**
       * Returns all valid, non-phantom Model instances that have been updated in the Store but not yet synchronized with the Proxy.
       * @return {Ext.data.Model[]} The updated Model instances
       */
      getUpdatedRecords: function() {
          return [];
      },
  
      /**
       * Gets all {@link Ext.data.Model records} added or updated since the last commit. Note that the order of records
       * returned is not deterministic and does not indicate the order in which records were modified. Note also that
       * removed records are not included (use {@link #getRemovedRecords} for that).
       * @return {Ext.data.Model[]} The added and updated Model instances
       */
      getModifiedRecords : function(){
          return [].concat(this.getNewRecords(), this.getUpdatedRecords());
      },
      
      /**
       * @private
       * Filter function for updated records.
       */
      filterUpdated: function(item) {
          // only want dirty records, not phantoms that are valid
          return item.dirty === true && item.phantom !== true && item.isValid();
      },
  
      /**
       * Returns any records that have been removed from the store but not yet destroyed on the proxy.
       * @return {Ext.data.Model[]} The removed Model instances. Note that this is a *copy* of the store's
       * array, so may be mutated.
       */
      getRemovedRecords: function() {
          var removed = this.getRawRemovedRecords();
          return removed ? Ext.Array.clone(removed) : [];
      },
  
      /**
       * Synchronizes the store with its {@link #proxy}. This asks the proxy to batch together any new, updated
       * and deleted records in the store, updating the store's internal representation of the records
       * as each operation completes.
       * 
       * @param {Object} [options] Object containing one or more properties supported by the sync method (these get 
       * passed along to the underlying proxy's {@link Ext.data.Proxy#batch batch} method):
       * 
       * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply 
       * to the created batch). If unspecified a default batch will be auto-created as needed.
       * 
       * @param {Function} [options.callback] The function to be called upon completion of the sync.
       * The callback is called regardless of success or failure and is passed the following parameters:
       * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
       * containing all operations in their current state after processing
       * @param {Object} options.callback.options The options argument that was originally passed into sync
       * 
       * @param {Function} [options.success] The function to be called upon successful completion of the sync. The 
       * success function is called only if no exceptions were reported in any operations. If one or more exceptions
       * occurred then the failure function will be called instead. The success function is called 
       * with the following parameters:
       * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
       * containing all operations in their current state after processing
       * @param {Object} options.success.options The options argument that was originally passed into sync
       * 
       * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the sync. The 
       * failure function is called when one or more operations returns an exception during processing (even if some
       * operations were also successful). In this case you can check the batch's {@link Ext.data.Batch#exceptions 
       * exceptions} array to see exactly which operations had exceptions. The failure function is called with the 
       * following parameters:
       * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch} that was processed, containing all
       * operations in their current state after processing
       * @param {Object} options.failure.options The options argument that was originally passed into sync
       * 
       * @param {Object} [options.params] Additional params to send during the sync Operation(s).
       *
       * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
       * the callback, success and/or failure functions). Defaults to the store's proxy.
       * 
       * @return {Ext.data.Store} this
       */
      sync: function(options) {
          var me = this,
              operations = {},
              toCreate = me.getNewRecords(),
              toUpdate = me.getUpdatedRecords(),
              toDestroy = me.getRemovedRecords(),
              needsSync = false;
  
          //<debug>
          if (me.isSyncing) {
              Ext.log.warn('Sync called while a sync operation is in progress. Consider configuring autoSync as false.');
          }
          //</debug>
  
          me.needsSync = false;
  
          if (toCreate.length > 0) {
              operations.create = toCreate;
              needsSync = true;
          }
  
          if (toUpdate.length > 0) {
              operations.update = toUpdate;
              needsSync = true;
          }
  
          if (toDestroy.length > 0) {
              operations.destroy = toDestroy;
              needsSync = true;
          }
  
          if (needsSync && me.fireEvent('beforesync', operations) !== false) {
              me.isSyncing = true;
  
              options = options || {};
  
              me.proxy.batch(Ext.apply(options, {
                  operations: operations,
                  listeners: me.getBatchListeners()
              }));
          }
  
          return me;
      },
      
      /**
       * @private
       * Returns an object which is passed in as the listeners argument to proxy.batch inside this.sync.
       * This is broken out into a separate function to allow for customisation of the listeners
       * @return {Object} The listeners object
       */
      getBatchListeners: function() {
          var me = this,
              listeners = {
                  scope: me,
                  exception: me.onBatchException,
                  complete: me.onBatchComplete
              };
  
          if (me.batchUpdateMode === 'operation') {
              listeners.operationcomplete = me.onBatchOperationComplete;
          }
  
          return listeners;
      },
  
      /**
       * Saves all pending changes via the configured {@link #proxy}. Use {@link #sync} instead.
       * @deprecated 4.0.0 Will be removed in the next major version
       */
      save: function() {
          return this.sync.apply(this, arguments);
      },
  
      /**
       * Marks this store as needing a load. When the current executing event handler exits,
       * this store will send a request to load using its configured {@link #proxy}.
       *
       * Upon return of the data from whatever data source the proxy connected to, the retrieved
       * {@link Ext.data.Model records} will be loaded into this store, and the optional callback will be called.
       * Example usage:
       *
       *     store.load({
       *         scope: this,
       *         callback: function(records, operation, success) {
       *             // the operation object
       *             // contains all of the details of the load operation
       *             console.log(records);
       *         }
       *     });
       *
       * If the callback scope does not need to be set, a function can simply be passed:
       *
       *     store.load(function(records, operation, success) {
       *         console.log('loaded records');
       *     });
       *
       * @param {Object} [options] This is passed into the {@link Ext.data.operation.Operation Operation}
       * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
       * In addition to the options listed below, this object may contain properties to configure the
       * {@link Ext.data.operation.Operation Operation}.
       * @param {Function} [options.callback] A function which is called when the response arrives.
       * @param {Ext.data.Model[]} options.callback.records Array of records.
       * @param {Ext.data.operation.Operation} options.callback.operation The Operation itself.
       * @param {Boolean} options.callback.success `true` when operation completed successfully.
       * @param {Boolean} [options.addRecords=false] Specify as `true` to *add* the incoming records rather than the
       * default which is to have the incoming records *replace* the existing store contents.
       * 
       * @return {Ext.data.Store} this
       * @since 1.1.0
       */
      load: function(options) {
          var me = this;
  
          // Legacy option. Specifying a function was allowed.
          if (typeof options === 'function') {
              options = {
                  callback: options
              };
          } else {
              // We may mutate the options object in setLoadOptions.
              options = options ? Ext.Object.chain(options) : {};
          }
  
          me.pendingLoadOptions = options;
  
          // If we are configured to load asynchronously (the default for async proxies)
          // then schedule a flush, unless one is already scheduled.
          if (me.getAsynchronousLoad()) {
              if (!me.loadTimer) {
                  me.loadTimer = Ext.asap(me.flushLoad, me);
              }
          }
          // If we are configured to load synchronously (the default for sync proxies)
          // then flush the load now.
          else {
              me.flushLoad();
          }
          return me;
      },
  
      /**
       * Called when the event handler which called the {@link #method-load} method exits.
       */
      flushLoad: function() {
          var me = this,
              options = me.pendingLoadOptions,
              operation;
  
          // If it gets called programatically before the timer fired, the listener will need cancelling.
          me.clearLoadTask();
          if (!options) {
              return;
          }
  
          me.setLoadOptions(options);
  
          if (me.getRemoteSort() && options.sorters) {
              me.fireEvent('beforesort', me, options.sorters);
          }
          
          operation = Ext.apply({
              internalScope: me,
              internalCallback: me.onProxyLoad,
              scope: me
          }, options);
          me.lastOptions = operation;
          
  
          operation = me.createOperation('read', operation);
  
          if (me.fireEvent('beforeload', me, operation) !== false) {
              me.onBeforeLoad(operation);
              me.loading = true;
              operation.execute();
          }
      },
  
      /**
       * Reloads the store using the last options passed to the {@link #method-load} method. You can use the reload method to reload the
       * store using the parameters from the last load() call. For example:
       *
       *     store.load({
       *         params : {
       *             userid : 22216
       *         }
       *     });
       *
       *     //...
       *
       *     store.reload();
       *
       * The initial {@link #method-load} execution will pass the `userid` parameter in the request. The {@link #reload} execution
       * will also send the same `userid` parameter in its request as it will reuse the `params` object from the last {@link #method-load} call.
       *
       * You can override a param by passing in the config object with the `params` object:
       *
       *     store.load({
       *         params : {
       *             userid : 22216,
       *             foo    : 'bar'
       *         }
       *     });
       *
       *     //...
       *
       *     store.reload({
       *         params : {
       *             userid : 1234
       *         }
       *     });
       *
       * The initial {@link #method-load} execution sends the `userid` and `foo` parameters but in the {@link #reload} it only sends
       * the `userid` paramter because you are overriding the `params` config not just overriding the one param. To only change a single param
       * but keep other params, you will have to get the last params from the {@link #lastOptions} property:
       *
       *     var lastOptions = store.lastOptions,
       *         lastParams = Ext.clone(lastOptions.params); // make a copy of the last params so we don't affect future reload() calls
       *
       *     lastParams.userid = 1234;
       *
       *     store.reload({
       *         params : lastParams
       *     });
       *
       * This will now send the `userid` parameter as `1234` and the `foo` param as `'bar'`.
       *
       * @param {Object} [options] A config object which contains options which may override the options passed to the previous load call. See the
       * {@link #method-load} method for valid configs.
       */
      reload: function(options) {
          return this.load(Ext.apply({}, options, this.lastOptions));
      },
  
      onEndUpdate: function() {
          var me = this;
  
          if (me.needsSync && me.autoSync && !me.autoSyncSuspended) {
              me.sync();
          }
      },
  
      /**
       * @private
       * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to..
       * @param {Ext.data.Model} record The model instance that was edited
       * @since 3.4.0
       */
      afterReject: function(record) {
          var me = this;
          // Must pass the 5th param (modifiedFieldNames) as null, otherwise the
          // event firing machinery appends the listeners "options" object to the arg list
          // which may get used as the modified fields array by a handler.
          // This array is used for selective grid cell updating by Grid View.
          // Null will be treated as though all cells need updating.
          if (me.contains(record)) {
              me.onUpdate(record, Ext.data.Model.REJECT, null);
              me.fireEvent('update', me, record, Ext.data.Model.REJECT, null);
          }
      },
  
      /**
       * @private
       * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
       * @param {Ext.data.Model} record The model instance that was edited
       * @since 3.4.0
       */
      afterCommit: function(record, modifiedFieldNames) {
          var me = this;
          if (!modifiedFieldNames) {
              modifiedFieldNames = null;
          }
          if (me.contains(record)) {
              me.onUpdate(record, Ext.data.Model.COMMIT, modifiedFieldNames);
              me.fireEvent('update', me, record, Ext.data.Model.COMMIT, modifiedFieldNames);
          }
      },
      
      afterErase: function(record) {
          this.onErase(record);
      },
      
      onErase: Ext.emptyFn,
  
      onUpdate: Ext.emptyFn,
  
      /**
       * @private
       */
      doDestroy: function() {
          var me = this,
              proxy = me.getProxy();
  
          me.clearLoadTask();
          me.getData().destroy();
          me.data = null;
          me.setProxy(null);
          
          if (proxy.autoCreated) {
              proxy.destroy();
          }
          
          me.setModel(null);
          
          me.callParent();
      },
  
      /**
       * Returns true if the store has a pending load task.
       * @return {Boolean} `true` if the store has a pending load task.
       * @private
       */
      hasPendingLoad: function() {
          return !!this.pendingLoadOptions || this.isLoading();
      },
  
      /**
       * Returns true if the Store is currently performing a load operation
       * @return {Boolean} `true` if the Store is currently loading
       */
      isLoading: function() {
          return !!this.loading;
      },
  
      /**
       * Returns `true` if the Store has been loaded.
       * @return {Boolean} `true` if the Store has been loaded.
       */
      isLoaded: function() {
          return this.loadCount > 0;
      },
  
      /**
       * Suspends automatically syncing the Store with its Proxy.  Only applicable if {@link #autoSync} is `true`
       */
      suspendAutoSync: function() {
          ++this.autoSyncSuspended;
      },
  
      /**
       * Resumes automatically syncing the Store with its Proxy.  Only applicable if {@link #autoSync} is `true`
       * @param {Boolean} syncNow Pass `true` to synchronize now. Only synchronizes with the Proxy if the suspension
       * count has gone to zero (We are not under a higher level of suspension)
       * 
       */
      resumeAutoSync: function(syncNow) {
          var me = this;
  
          //<debug>
          if (!me.autoSyncSuspended) {
              Ext.log.warn('Mismatched call to resumeAutoSync - auto synchronization is currently not suspended.');
          }
          //</debug>
          if (me.autoSyncSuspended && ! --me.autoSyncSuspended) {
              if (syncNow) {
                  me.sync();
              }
          }
      },
  
      /**
       * Removes all records from the store. This method does a "fast remove",
       * individual remove events are not called. The {@link #clear} event is
       * fired upon completion.
       * @method
       * @since 1.1.0
       */
      removeAll: Ext.emptyFn,
      // individual store subclasses should implement a "fast" remove
      // and fire a clear event afterwards
      
      // to be implemented by subclasses
      clearData: Ext.emptyFn,
  
      privates: {
          /**
           * @private
           * Returns the array of records which have been removed since the last time this store was synced.
           *
           * This is used internally, when purging removed records after a successful sync.
           * This is overridden by TreeStore because TreeStore accumulates deleted records on removal
           * of child nodes from their parent, *not* on removal of records from its collection. The collection
           * has records added on expand, and removed on collapse.
           */
          getRawRemovedRecords: function() {
              return this.removed;
          },
  
          onExtraParamsChanged: function() {
              
          },
  
          clearLoadTask: function() {
              if (this.loadTimer) {
                  Ext.asapCancel(this.loadTimer);
              }
              
              this.pendingLoadOptions = this.loadTimer = null;
          },
  
          cleanRemoved: function() {
              // Must use class-specific getRawRemovedRecords.
              // Regular Stores add to the "removed" property on remove.
              // TreeStores are having records removed all the time; node collapse removes.
              // TreeStores add to the "removedNodes" property onNodeRemove
              var removed = this.getRawRemovedRecords(),
                  len, i;
  
              if (removed) {
                  for (i = 0, len = removed.length; i < len; ++i) {
                      removed[i].unjoin(this);
                  }
                  removed.length = 0;
              }
          },
  
          createOperation: function(type, options) {
              var me = this,
                  proxy = me.getProxy(),
                  listeners;
  
              if (!me.proxyListeners) {
                  listeners = {
                      scope: me,
                      destroyable: true,
                      beginprocessresponse: me.beginUpdate,
                      endprocessresponse: me.endUpdate
                  };
  
                  if (!me.disableMetaChangeEvent) {
                      listeners.metachange = me.onMetaChange;
                  }
                  me.proxyListeners = proxy.on(listeners);
              }
              return proxy.createOperation(type, options);
          },
  
          createImplicitModel: function(fields) {
              var me = this,
                  modelCfg = {
                      extend: me.implicitModel,
                      statics: {
                          defaultProxy: 'memory'
                      }
                  },
                  proxy, model;
  
              if (fields) {
                  modelCfg.fields = fields;
              }
              model = Ext.define(null, modelCfg);
  
              me.setModel(model);
  
              proxy = me.getProxy();
              if (proxy) {
                  model.setProxy(proxy);
              } else {
                  me.setProxy(model.getProxy());
              }
          },
  
          loadsSynchronously: function() {
              return this.getProxy().isSynchronous;
          },
  
          onBeforeLoad: Ext.privateFn,
  
          removeFromRemoved: function(record) {
              // Must use class-specific getRawRemovedRecords.
              // Regular Stores add to the "removed" property on remove.
              // TreeStores are having records removed all the time; node collapse removes.
              // TreeStores add to the "removedNodes" property onNodeRemove
              var removed = this.getRawRemovedRecords();
              if (removed) {
                  Ext.Array.remove(removed, record);
                  record.unjoin(this);
              }
          },
  
          setLoadOptions: function(options) {
              var me = this,
                  filters, sorters;
  
              if (me.getRemoteFilter()) {
                  filters = me.getFilters(false);
                  if (filters && filters.getCount()) {
                      options.filters = filters.getRange();
                  }
              }
  
              if (me.getRemoteSort()) {
                  sorters = me.getSorters(false);
                  if (sorters && sorters.getCount()) {
                      options.sorters = sorters.getRange();
                  }
              }
          }
      }
  
  });
  