text: /**
   * A chained store is a store that is a "view" of an existing store. The data comes from the
   * {@link #source}, however this view of the store may be sorted & filtered independently without
   * having any impact on the {@link #source} store.
   */
  Ext.define('Ext.data.ChainedStore', {
      extend: 'Ext.data.AbstractStore',
      alias: 'store.chained',
  
      /**
       * @property {Boolean} isChainedStore
       * `true` in this class to identify an object as an instantiated ChainedStore, or subclass thereof.
       */
      isChainedStore: true,
  
      config: {
          /**
           * @cfg {Ext.data.Store/String} source
           * The backing data source for this chained store. Either a store instance
           * or the id of an existing store.
           */
          source: null,
  
          remoteFilter: false,
  
          remoteSort: false
      },
  
      mixins: [
          'Ext.data.LocalStore'
      ],
      
      //<debug>
      updateRemoteFilter: function(remoteFilter, oldRemoteFilter) {
          if (remoteFilter) {
              Ext.raise('Remote filtering cannot be used with chained stores.');
          }
          this.callParent([remoteFilter, oldRemoteFilter]);
      },
  
      updateRemoteSort: function(remoteSort, oldRemoteSort) {
          if (remoteSort) {
              Ext.raise('Remote sorting cannot be used with chained stores.');
          }
          this.callParent([remoteSort, oldRemoteSort]);
      },
      //</debug>
      
      remove: function() {
          var source = this.getSource();
          //<debug>
          if (!source) {
              Ext.raise('Cannot remove records with no source.');
          }
          //</debug>
          return source.remove.apply(source, arguments);
      },
  
      removeAll: function() {
          var source = this.getSource();
          //<debug>
          if (!source) {
              Ext.raise('Cannot remove records with no source.');
          }
          //</debug>
          return source.removeAll();
      },
      
      getData: function() {
          var me = this,
              data = me.data;
          
          if (!data) {
              me.data = data = me.constructDataCollection();
          }
          return data;
      },
  
      getTotalCount: function() {
          return this.getCount();
      },
  
      getSession: function() {
          return this.getSourceValue('getSession', null);
      },
  
      applySource: function(source) {
          if (source) {
              //<debug>
              var original = source,
                  s;
              //</debug>
              source = Ext.data.StoreManager.lookup(source);
              //<debug>
              if (!source) {
                  s = 'Invalid source {0}specified for Ext.data.ChainedStore';
                  s = Ext.String.format(s, typeof original === 'string' ? '"' + original + '" ' : '');
                  Ext.raise(s);
              }
              //</debug>
          }
          return source;
      },
      
      updateSource: function(source, oldSource) {
          var me = this,
              data;
          
          if (oldSource && !oldSource.destroyed) {
              oldSource.removeObserver(me);
          }
          
          if (source) {
              data = me.getData();
              data.setSource(source.getData());
              if (!me.isInitializing) {
                  me.fireEvent('refresh', me);
                  me.fireEvent('datachanged', me);
              }
              source.addObserver(me);
          }
      },
      
      /**
       * Get the model used for this store.
       * @return {Ext.data.Model} The model
       */
      getModel: function() {
          return this.getSourceValue('getModel', null);
      },
  
      getProxy: function() {
          return null;
      },
      
      onCollectionAdd: function(collection, info) {
          var me = this,
              records = info.items,
              lastChunk = !info.next;
  
          if (me.ignoreCollectionAdd) {
              return;
          }
  
          // Collection add changes the items reference of the collection, and that array
          // object if directly referenced by Ranges. The ranges have to refresh themselves
          // upon add.
          if (me.activeRanges) {
              me.syncActiveRanges();
          }
  
          me.fireEvent('add', me, records, info.at);
          // If there is a next property, that means there is another range that needs
          // to be removed after this. Wait until everything is gone before firign datachanged
          // since it should be a bulk operation
          if (lastChunk) {
              me.fireEvent('datachanged', me);
          }
      },
  
      // Our collection tells us that an item has changed
      onCollectionItemChange: function(collection, info) {
          var me = this,
              record = info.item,
              modifiedFieldNames = info.modified || null,
              type = info.meta;
  
          // Inform any interested parties that a record has been mutated.
          // This will be invoked on TreeStores in which the invoking record
          // is an descendant of a collapsed node, and so *will not be contained by this store
          me.onUpdate(record, type, modifiedFieldNames, info);
          me.fireEvent('update', me, record, type, modifiedFieldNames, info);
          me.fireEvent('datachanged', me);
      },
      
      onCollectionUpdateKey: function(source, details) {
          // Must react to upstream Collection key update by firing idchanged event
          this.fireEvent('idchanged', this, details.item, details.oldKey, details.newKey);
      },
  
      onUpdate: Ext.emptyFn,
  
      onCollectionRemove: function(collection, info) {
          var me = this,
              records = info.items,
              lastChunk = !info.next;
          
          if (me.ignoreCollectionRemove) {
              return;
          }
          
          me.fireEvent('remove', me, records, info.at, false);
          // If there is a next property, that means there is another range that needs
          // to be removed after this. Wait until everything is gone before firign datachanged
          // since it should be a bulk operation
          if (lastChunk) {
              me.fireEvent('datachanged', me);
          }
      },
  
      onSourceBeforeLoad: function(source, operation) {
          this.fireEvent('beforeload', this, operation);
          this.callObservers('BeforeLoad', [operation]);
      },
  
      onSourceAfterLoad: function(source, records, successful, operation) {
          this.fireEvent('load', this, records, successful, operation);
          this.callObservers('AfterLoad', [records, successful, operation]);
      },
  
      onFilterEndUpdate: function() {
          this.callParent(arguments);
          this.callObservers('Filter');
      },
      
      onSourceBeforePopulate: function() {
          this.ignoreCollectionAdd = true;
          this.callObservers('BeforePopulate');
      },
      
      onSourceAfterPopulate: function() {
          var me = this;
          me.ignoreCollectionAdd = false;
          me.fireEvent('datachanged', me);
          me.fireEvent('refresh', me);
          this.callObservers('AfterPopulate');
      },
      
      onSourceBeforeClear: function() {
          this.ignoreCollectionRemove = true;
          this.callObservers('BeforeClear');
      },
      
      onSourceAfterClear: function() {
          this.ignoreCollectionRemove = false;
          this.callObservers('AfterClear');
      },
      
      onSourceBeforeRemoveAll: function() {
          this.ignoreCollectionRemove = true;
          this.callObservers('BeforeRemoveAll');
      },
      
      onSourceAfterRemoveAll: function(source, silent) {
          var me = this;
          me.ignoreCollectionRemove = false;
          if (!silent) {
              me.fireEvent('clear', me);
              me.fireEvent('datachanged', me);
          }
          this.callObservers('AfterRemoveAll', [silent]);
      },
  
      onSourceFilter: function() {
          var me = this;
          me.fireEvent('refresh', me);
          me.fireEvent('datachanged', me);
      },
      
      hasPendingLoad: function() {
          return this.getSourceValue('hasPendingLoad', false);
      },
      
      isLoaded: function() {
          return this.getSourceValue('isLoaded', false);
      },
  
      isLoading: function() {
          return this.getSourceValue('isLoading', false);
      },
  
      doDestroy: function() {
          var me = this;
  
          me.observers = null;
          me.setSource(null);
          me.getData().destroy(true);
          me.data = null;
          
          me.callParent();
      },
  
      privates: {
          getSourceValue: function(method, defaultValue) {
              var source = this.getSource(),
                  val = defaultValue;
  
              if (source && !source.destroyed) {
                  val = source[method]();
              }
              return val;
          },
  
          isMoving: function () {
              var source = this.getSource();
              return source.isMoving ? source.isMoving.apply(source, arguments) : false;
          },
  
          loadsSynchronously: function() {
              return this.getSource().loadsSynchronously();
          }
      }
  
      // Provides docs from the mixin
      
      /**
       * @method add
       * @inheritdoc Ext.data.LocalStore#add
       */
  
      /**
       * @method each
       * @inheritdoc Ext.data.LocalStore#each
       */
  
      /**
       * @method collect
       * @inheritdoc Ext.data.LocalStore#collect
       */
  
      /**
       * @method getById
       * @inheritdoc Ext.data.LocalStore#getById
       */
  
      /**
       * @method getByInternalId
       * @inheritdoc Ext.data.LocalStore#getByInternalId
       */
  
      /**
       * @method indexOf
       * @inheritdoc Ext.data.LocalStore#indexOf
       */
  
      /**
       * @method indexOfId
       * @inheritdoc Ext.data.LocalStore#indexOfId
       */
      
      /**
       * @method insert
       * @inheritdoc Ext.data.LocalStore#insert
       */
  
      /**
       * @method queryBy
       * @inheritdoc Ext.data.LocalStore#queryBy
       */
  
      /**
       * @method query
       * @inheritdoc Ext.data.LocalStore#query
       */
  
      /**
       * @method first
       * @inheritdoc Ext.data.LocalStore#first
       */
  
      /**
       * @method last
       * @inheritdoc Ext.data.LocalStore#last
       */
  
      /**
       * @method sum
       * @inheritdoc Ext.data.LocalStore#sum
       */
  
      /**
       * @method count
       * @inheritdoc Ext.data.LocalStore#count
       */
  
      /**
       * @method min
       * @inheritdoc Ext.data.LocalStore#min
       */
  
      /**
       * @method max
       * @inheritdoc Ext.data.LocalStore#max
       */
  
      /**
       * @method average
       * @inheritdoc Ext.data.LocalStore#average
       */
  
      /**
       * @method aggregate
       * @inheritdoc Ext.data.LocalStore#aggregate
       */
  });