    /** * This class manages arbitrary data and its relationship to data models. Instances of * `ViewModel` are associated with some `Component` and then used by their child items * for the purposes of Data Binding. *  * # Binding *  * The most commonly used aspect of a `ViewModel` is the `bind` method. This method takes * a "bind descriptor" (see below) and a callback to call when the data indicated by the * bind descriptor either becomes available or changes. * * The `bind` method, based on the bind descriptor given, will return different types of * "binding" objects. These objects maintain the connection between the requested data and * the callback. Bindings ultimately derive from `{@link Ext.app.bind.BaseBinding}` * which provides several methods to help manage the binding. * * Perhaps the most important method is `destroy`. When the binding is no longer needed * it is important to remember to `destroy` it. Leaking bindings can cause performance * problems or worse when callbacks are called at unexpected times. * * The types of bindings produced by `bind` are: * *   * `{@link Ext.app.bind.Binding}` *   * `{@link Ext.app.bind.Multi}` *   * `{@link Ext.app.bind.TemplateBinding}` * * ## Bind Descriptors *  * A "bind descriptor" is a value (a String, an Object or an array of these) that describe * the desired data. Any piece of data in the `ViewModel` can be described by a bind * descriptor. *  * ### Textual Bind Descriptors *  * The simplest and most common form of bind descriptors are strings that look like an * `Ext.Template` containing text and tokens surrounded by "{}" with dot notation inside * to traverse objects and their properties. *  * For example: *  *   * `'Hello {user.name}!'` *   * `'You have selected "{selectedItem.text}".'` *   * `'{!isDisabled}'` *   * `'{a > b ? "Bigger" : "Smaller"}'` *   * `'{user.groups}'` * * All except the last are `{@link Ext.app.bind.TemplateBinding template bindings}` * which use the familiar `Ext.Template` syntax with some slight differences. For more on * templates see `{@link Ext.app.bind.Template}`. * * The last descriptor is called a "direct bind descriptor". This special form of * bind maps one-to-one to some piece of data in the `ViewModel` and is managed by the * `{@link Ext.app.bind.Binding}` class. * * #### Two-Way Descriptors * * A direct bind descriptor may be able to write back a value to the `ViewModel` as well * as retrieve one. When this is the case, they are said to be "two-way". For example: * *      var binding = viewModel.bind('{s}', function(s) { console.log('s=' + s); }); * *      binding.setValue('abc'); * * Direct use of `ViewModel` in this way is not commonly needed because `Ext.Component` * automates this process. For example, a `textfield` component understands when it is * given a "two-way" binding and automatically synchronizes its value bidirectionally using * the above technique. For example: * *      Ext.widget({ *          items: [{ *              xtype: 'textfield', *              bind: '{s}'  // a two-way / direct bind descriptor *          }] *      }); * * ### Object and Array Descriptors / Multi-Bind * * With two exceptions (see below) an Object is interpreted as a "shape" to produce by * treating each of its properties as individual bind descriptors. An object of the same * shape is passed as the value of the bind except that each property is populated with * the appropriate value. Of course, this definition is recursive, so these properties * may also be objects. * * For example: * *      viewModel.bind({ *              x: '{x}', *              foo: { *                  bar: 'Hello {foo.bar}' *              } *          }, *          function (obj) { *              //  obj = { *              //      x: 42, *              //      foo: { *              //          bar: 'Hello foobar' *              //      } *              //  } *          }); * * Arrays are handled in the same way. Each element of the array is considered a bind * descriptor (recursively) and the value produced for the binding is an array with each * element set to the bound property. * * ### Bind Options * * One exception to the "object is a multi-bind" rule is when that object contains a * `bindTo` property. When an object contains a `bindTo` property the object is understood * to contain bind options and the value of `bindTo` is considered the actual bind * descriptor. * * For example: * *      viewModel.bind({ *              bindTo: '{x}', *              single: true *          }, *          function (x) { *              console.log('x: ' + x); // only called once *          }); * * The available bind options depend on the type of binding, but since all bindings * derive from `{@link Ext.app.bind.BaseBinding}` its options are always applicable. * For a list of the other types of bindings, see above. * * #### Deep Binding * * When a direct bind is made and the bound property is an object, by default the binding * callback is only called when that reference changes. This is the most efficient way to * understand a bind of this type, but sometimes you may need to be notified if any of the * properties of that object change. * * To do this, we create a "deep bind": * *      viewModel.bind({ *              bindTo: '{someObject}', *              deep: true *          }, *          function (someObject) { *              // called when reference changes or *any* property changes *          }); * * #### Binding Timings * * The `ViewModel` has a {@link #scheduler} attached that is used to coordinate the firing of bindings. * It serves 2 main purposes: * - To coordinate dependencies between bindings. This means bindings will be fired in an order such that * the any dependencies for a binding are fired before the binding itself. * - To batch binding firings. The scheduler runs on a short timer, so the following code will only trigger * a single binding (the last), the changes in between will never be triggered. *  *     viewModel.bind('{val}', function(v) { *         console.log(v); *     }); *     viewModel.set('val', 1); *     viewModel.set('val', 2); *     viewModel.set('val', 3); *     viewModel.set('val', 4); * * The `ViewModel` can be forced to process by calling `{@link #notify}`, which will force the * scheduler to run immediately in the current state. *  *     viewModel.bind('{val}', function(v) { *         console.log(v); *     }); *     viewModel.set('val', 1); *     viewModel.notify(); *     viewModel.set('val', 2); *     viewModel.notify(); *     viewModel.set('val', 3); *     viewModel.notify(); *     viewModel.set('val', 4); *     viewModel.notify(); *   * * #### Models, Stores and Associations * * A {@link Ext.data.Session Session} manages model instances and their associations. * The `ViewModel` may be used with or without a `Session`. When a `Session` is attached, the * `ViewModel` will always consult the `Session` to ask about records and stores. The `Session` * ensures that only a single instance of each model Type/Id combination is created. This is  * important when tracking changes in models so that we always have the same reference. * * A `ViewModel` provides functionality to easily consume the built in data package types * {@link Ext.data.Model} and {@link Ext.data.Store}, as well as their associations. * * ### Model Links * * A model can be described declaratively using a {@link #links `link`}. In the example code below, * We ask the `ViewModel` to construct a record of type `User` with `id: 17`. The model will be loaded * from the server and the bindings will trigger once the load has completed. Similarly, we could also * attach a model instance to the `ViewModel` data directly. * *     Ext.define('MyApp.model.User', { *         extend: 'Ext.data.Model', *         fields: ['name'] *     }); *      *     var rec = new MyApp.model.User({ *         id: 12, *         name: 'Foo' *     }); *      *     var viewModel = new Ext.app.ViewModel({ *         links: { *             theUser: { *                 type: 'User', *                 id: 17 *             } *         }, *         data: { *             otherUser: rec *         } *     }); *     viewModel.bind('{theUser.name}', function(v) { *         console.log(v); *     }); *     viewModel.bind('{otherUser.name}', function(v) { *         console.log(v); *     }); * * ### Model Fields * * Bindings have the functionality to inspect the parent values and resolve the underlying * value dynamically. This behavior allows model fields to be interrogated as part of a binding. * *     Ext.define('MyApp.model.User', { *         extend: 'Ext.data.Model', *         fields: ['name', 'age'] *     }); * *     var viewModel = new Ext.app.ViewModel({ *         links: { *             theUser: { *                 type: 'User', *                 id: 22 *             } *         } *     }); * *     // Server responds with: *     { *         "id": 22, *         "name": "Foo", *         "age": 100 *     } * *     viewModel.bind('Hello {name}, you are {age} years old', function(v) { *         console.log(v); *     }); * * ### Associations * * In the same way as fields, the bindings can also traverse associations in a bind statement. * The `ViewModel` will handle the asynchronous loading of data and only present the value once * the full path has been loaded. For more information on associations see {@link Ext.data.schema.OneToOne OneToOne} and * {@link Ext.data.schema.ManyToOne ManyToOne} associations. * *     Ext.define('User', { *         extend: 'Ext.data.Model', *         fields: ['name'] *     }); * *     Ext.define('Order', { *         extend: 'Ext.data.Model', *         fields: ['date', { *             name: 'userId', *             reference: 'User' *         }] *     }); * *     Ext.define('OrderItem', { *         extend: 'Ext.data.Model', *         fields: ['price', 'qty', { *             name: 'orderId', *             reference: 'Order' *         }] *     }); * *     var viewModel = new Ext.app.ViewModel({ *         links: { *             orderItem: { *                 type: 'OrderItem', *                 id: 13 *             } *         } *     }); *     // The viewmodel will handle both ways of loading the data: *     // a) If the data is loaded inline in a nested fashion it will *     //    not make requests for extra data *     // b) Only loading a single model at a time. So the Order will be loaded once *     //    the OrderItem returns. The User will be loaded once the Order loads. *     viewModel.bind('{orderItem.order.user.name}', function(name) { *         console.log(name); *     }); * * ### Stores * * Stores can be created as part of the `ViewModel` definition. The definitions are processed * like bindings which allows for very powerful dynamic functionality. * * It is important to ensure that you name viewModel's data keys uniquely. If data is not named   * uniquely, binds and formulas may receive information from an unintended data source.   * This applies to keys in the viewModel's data block, stores, and links configs. * *     var viewModel = new Ext.app.ViewModel({ *         stores: { *             users: { *                 model: 'User', *                 autoLoad: true, *                 filters: [{ *                     property: 'createdDate', *                     value: '{createdFilter}', *                     operator: '>' *                 }] *             } *         } *     }); *     // Later on in our code, we set the date so that the store is created. *     viewModel.set('createdFilter', Ext.Date.subtract(new Date(), Ext.Date.DAY, 7)); * * See {@link #stores} for more detail. * * #### Formulas * * Formulas allow for calculated `ViewModel` data values. The dependencies for these formulas * are automatically determined so that the formula will not be processed until the required * data is present. * *     var viewModel = new Ext.app.ViewModel({ *         formulas: { *             fullName: function(get) { *                 return get('firstName') + ' ' + get('lastName'); *             } *         }, *         data: {firstName: 'John', lastName: 'Smith'} *     }); * *     viewModel.bind('{fullName}', function(v) { *         console.log(v); *     }); * * See {@link #formulas} for more detail. */Ext.define('Ext.app.ViewModel', {    mixins: [        'Ext.mixin.Factoryable',        'Ext.mixin.Identifiable'    ],     requires: [        'Ext.util.Scheduler',        'Ext.data.Session',        'Ext.app.bind.RootStub',        'Ext.app.bind.LinkStub',        'Ext.app.bind.Multi',        'Ext.app.bind.Formula',        'Ext.app.bind.TemplateBinding',        // TODO: this is an injected dependency in onStoreBind, need to define so          // cmd can detect it         'Ext.data.ChainedStore'    ],     alias: 'viewmodel.default', // also configures Factoryable      isViewModel: true,     factoryConfig: {        name: 'viewModel'    },     collectTimeout: 100,     expressionRe: /^(?:\{(?:(\d+)|([a-z_][\w\-\.]*))\})$/i,     $configStrict: false, // allow "formulas" to be specified on derived class body     config: {        /**         * @cfg {Object} data          * This object holds the arbitrary data that populates the `ViewModel` and is         * then available for binding.         * @since 5.0.0         */        data: true,         /**         * @cfg {Object} formulas          * An object that defines named values whose value is managed by function calls.         * The names of the properties of this object are assigned as values in the         * ViewModel.         *         * For example:         *         *      formulas: {         *          xy: function (get) { return get('x') * get('y'); }         *      }         *         * For more details about defining a formula, see `{@link Ext.app.bind.Formula}`.         * @since 5.0.0         */        formulas: {            $value: null,            merge: function (newValue, currentValue, target, mixinClass) {                return this.mergeNew(newValue, currentValue, target, mixinClass);            }        },         /**         * @cfg {Object} links          * Links provide a way to assign a simple name to a more complex bind. The primary         * use for this is to assign names to records in the data model.         *         *      links: {         *          theUser: {         *              type: 'User',         *              id: 12         *          }         *      }         *         * It is also possible to force a new phantom record to be created by not specifying an         * id but passing `create: true` as part of the descriptor. This is often useful when         * creating a new record for a child session.         *         *     links: {         *         newUser: {         *             type: 'User',         *             create: true         *         }         *     }          *         * `create` can also be an object containing initial data for the record.         *         *     links: {         *         newUser: {         *             type: 'User',         *             create: {         *                 firstName: 'John',         *                 lastName: 'Smith'         *             }         *         }         *     }          *         * While that is the typical use, the value of each property in `links` may also be         * a bind descriptor (see `{@link #method-bind}` for the various forms of bind         * descriptors).         * @since 5.0.0         */        links: null,         /**         * @cfg {Ext.app.ViewModel} parent         * The parent `ViewModel` of this `ViewModel`. Once set, this cannot be changed.         * @readonly         * @since 5.0.0         */        parent: null,         /**         * @cfg {Ext.app.bind.RootStub} root         * A reference to the root "stub" (an object that manages bindings).         * @private         * @since 5.0.0         */        root: true,         /**         * @cfg {Ext.util.Scheduler} scheduler         * The scheduler used to schedule and manage the delivery of notifications for         * all connections to this `ViewModel` and any other attached to it. The normal         * process to initialize the `scheduler` is to get the scheduler used by the         * `parent` or `session` and failing either of those, create one.         * @readonly         * @private         * @since 5.0.0         */        scheduler: null,         /**         * @cfg {String/Ext.data.schema.Schema} schema         * The schema to use for getting information about entities.         */        schema: 'default',         /**         * @cfg {Ext.data.Session} session         * The session used to manage the data model (records and stores).         * @since 5.0.0         */        session: null,         // @cmd-auto-dependency {isKeyedObject: true, aliasPrefix: "store.", defaultType: "store"}         /**         * @cfg {Object} stores          * A declaration of `Ext.data.Store` configurations that are first processed as         * binds to produce an effective store configuration.         *         * A simple store definition. We can reference this in our bind statements using the         * `{users}` as we would with other data values.         *         *     new Ext.app.ViewModel({         *         stores: {         *             users: {         *                 model: 'User',         *                 autoLoad: true         *             }         *         }         *     });         *         * This store definition contains a dynamic binding. The store will not be created until         * the initial value for groupId is set. Once that occurs, the store is created with the appropriate         * filter configuration. Subsequently, once we change the group value, the old filter will be         * overwritten with the new value.         *         *     var viewModel = new Ext.app.ViewModel({         *         stores: {         *             users: {         *                 model: 'User',         *                 filters: [{         *                     property: 'groupId',         *                     value: '{groupId}'         *                 }]         *             }         *         }         *     });         *     viewModel.set('groupId', 1); // This will trigger the store creation with the filter.         *     viewModel.set('groupId', 2); // The filter value will be changed.         *         * This store uses {@link Ext.data.ChainedStore store chaining} to create a store backed by the         * data in another store. By specifying a string as the store, it will bind our creation and backing         * to the other store. This functionality is especially useful when wanting to display a different "view"         * of a store, for example a different sort order or different filters.         *         *     var viewModel = new Ext.app.ViewModel({         *         stores: {         *             allUsers: {         *                 model: 'User',         *                 autoLoad: true         *             },         *             children: {         *                 source: '{allUsers}',         *                 filters: [{         *                     property: 'age',         *                     value: 18,         *                     operator: '<'         *                 }]         *             }         *         }         *     });         *         * @since 5.0.0         */        stores: null,         /**         * @cfg {Ext.container.Container} view         * The Container that owns this `ViewModel` instance.         * @since 5.0.0         */        view: null    },     constructor: function (config) {        // Used to track non-stub bindings         this.bindings = {};        /*         *  me.data = {         *      foo: {         *      },         *                   *      selectedUser: {         *          name: null         *      },         *  }         *         *  me.root = new Ext.app.bind.RootStub({         *      children: {         *          foo: new Ext.app.bind.Stub(),         *          selectedUser: new Ext.app.bind.LinkStub({         *              binding: session.bind(...),         *              children: {         *                  name: : new Ext.app.bind.Stub()         *              }         *          }),         *      }         *  })         */         this.initConfig(config);    },     destroy: function () {        var me = this,            scheduler = me._scheduler,            stores = me.storeInfo,            parent = me.getParent(),            task = me.collectTask,            children = me.children,            bindings = me.bindings,            key, store, autoDestroy;         me.destroying = true;        if (task) {            task.cancel();            me.collectTask = null;        }         // When used with components, they are destroyed bottom up         // so this scenario is only likely to happen in the case where         // we're using the VM without any component attachment, in which case         // we need to clean up here.         if (children) {            for (key in children) {                children[key].destroy();            }        }         if (stores) {            for (key in stores) {                store = stores[key];                autoDestroy = store.autoDestroy;                if (autoDestroy || (!store.$wasInstance && autoDestroy !== false)) {                    store.destroy();                }                Ext.destroy(store.$binding);            }        }         if (parent) {            parent.unregisterChild(me);        }                 me.getRoot().destroy();         for (key in bindings) {            bindings[key].destroy();        }         if (scheduler && scheduler.$owner === me) {            scheduler.$owner = null;            scheduler.destroy();        }         me.children = me.storeInfo = me._session = me._view = me._scheduler =                      me.bindings = me._root = me._parent = me.formulaFn = me.$formulaData = null;         me.destroying = false;        me.callParent();    },     /**     * This method requests that data in this `ViewModel` be delivered to the specified     * `callback`. The data desired is given in a "bind descriptor" which is the first     * argument.     *     * A simple call might look like this:     *     *     var binding = vm.bind('{foo}', this.onFoo, this);     *      *     binding.destroy();  // when done with the binding     *     * Options for the binding can be provided in the last argument:     *     *     var binding = vm.bind('{foo}', this.onFoo, this, {     *         deep: true     *     });     *      * Alternatively, bind options can be combined with the bind descriptor using only     * the first argument:     *     *     var binding = vm.bind({     *         bindTo: '{foo}',  // the presence of bindTo identifies this form     *         deep: true     *     }, this.onFoo, this);     *      * See the class documentation for more details on Bind Descriptors and options.     *     * @param {String/Object/Array} descriptor The bind descriptor. See class description     * for details.     * @param {Function} callback The function to call with the value of the bound property.     * @param {Object} [scope] The scope (`this` pointer) for the `callback`.     * @param {Object} [options] Additional options to configure the {@link Ext.app.bind.Binding binding}.     * If this parameter is provided, the `bindTo` form of combining options and bind descriptor is not     * recognized.     * @return {Ext.app.bind.BaseBinding/Ext.app.bind.Binding} The binding.     */    bind: function (descriptor, callback, scope, options) {        var me = this,            track = true,            binding;         scope = scope || me;         if (!options && descriptor.bindTo !== undefined && !Ext.isString(descriptor)) {            options = descriptor;            descriptor = options.bindTo;        }         if (!Ext.isString(descriptor)) {            binding = new Ext.app.bind.Multi(descriptor, me, callback, scope, options);        } else if (me.expressionRe.test(descriptor)) {            // If we have '{foo}' alone it is a literal             descriptor = descriptor.substring(1, descriptor.length - 1);            binding = me.bindExpression(descriptor, callback, scope, options);            track = false;        } else {            binding = new Ext.app.bind.TemplateBinding(descriptor, me, callback, scope, options);        }         if (track) {            me.bindings[binding.id] = binding;        }         return binding;    },     /**     * Gets the session attached to this (or a parent) ViewModel. See the {@link #session} configuration.     * @return {Ext.data.Session} The session. `null` if no session exists.     */    getSession: function () {        var me = this,            session = me._session,            parent;         if (!session && (parent = me.getParent())) {            me.setSession(session = parent.getSession());        }         return session || null;    },        /**     * Gets a store configured via the {@link #stores} configuration.     * @param {String} key The name of the store.     * @return {Ext.data.Store} The store. `null` if no store exists.     */    getStore: function(key) {        var storeInfo = this.storeInfo,            store;                if (storeInfo) {            store = storeInfo[key];        }        return store || null;    },        /**     * @method getStores     * @hide     */     /**     * Create a link to a reference. See the {@link #links} configuration.     * @param {String} key The name for the link.     * @param {Object} reference The reference descriptor.     */    linkTo: function (key, reference) {        var me = this,            stub, create, id, modelType, linkStub, rec;         //<debug>         if (key.indexOf('.') > -1) {            Ext.raise('Links can only be at the top-level: "' + key + '"');        }        //</debug>          if (reference.isModel) {            reference = {                type: reference.entityName,                id: reference.id            };        }        // reference is backwards compat, type is preferred.         modelType = reference.type || reference.reference;        create = reference.create;        if (modelType) {            // It's a record             id = reference.id;            //<debug>             if (!reference.create && Ext.isEmpty(id)) {                Ext.raise('No id specified. To create a phantom model, specify "create: true" as part of the reference.');            }            //</debug>             if (create) {                id = undefined;            }            rec = me.getRecord(modelType, id);            if (Ext.isObject(create)) {                rec.set(create);                rec.commit();                rec.phantom = true;            }            // Force creation at the root level. If an existing stub is there             // it will be grafted in place here.             stub = me.getRoot().createStubChild(key);            stub.set(rec);        } else {            stub = me.getStub(key);            if (!stub.isLinkStub) {                // Pass parent=null since we will graft in this new stub to replace us:                 linkStub = new Ext.app.bind.LinkStub(me, stub.name);                stub.graft(linkStub);                stub = linkStub;            }            stub.link(reference);        }    },     /**     * Forces all bindings in this ViewModel hierarchy to evaluate immediately. Use this to do a synchronous flush     * of all bindings.     */    notify: function () {        var scheduler = this.getScheduler();        if (!scheduler.firing) {            scheduler.notify();        }    },     /**     * Get a value from the data for this viewmodel.     * @param {String} path The path of the data to retrieve.     *     *    var value = vm.get('theUser.address.city');     *     * @return {Object} The data stored at the passed path.     */    get: function(path) {        return this.getStub(path).getValue();    },     /**     * Set  a value in the data for this viewmodel.     * @param {Object/String} path The path of the value to set, or an object literal to set     * at the root of the viewmodel.     * @param {Object} value The data to set at the value. If the value is an object literal,     * any required paths will be created.     *     *     // Set a single property at the root level     *     viewModel.set('expiry', Ext.Date.add(new Date(), Ext.Date.DAY, 7));     *     console.log(viewModel.get('expiry'));     *     // Sets a single property in user.address, does not overwrite any hierarchy.     *     viewModel.set('user.address.city', 'London');     *     console.log(viewModel.get('user.address.city'));     *     // Sets 2 properties of "user". Overwrites any existing hierarchy.     *     viewModel.set('user', {firstName: 'Foo', lastName: 'Bar'});     *     console.log(viewModel.get('user.firstName'));     *     // Sets a single property at the root level. Overwrites any existing hierarchy.     *     viewModel.set({rootKey: 1});     *     console.log(viewModel.get('rootKey'));     */    set: function (path, value) {        var me = this,            obj, stub;         // Force data creation         me.getData();         if (value === undefined && path && path.constructor === Object) {            stub = me.getRoot();            value = path;        } else if (path && path.indexOf('.') < 0) {            obj = {};            obj[path] = value;            value = obj;            stub = me.getRoot();        } else {            stub = me.getStub(path);        }         stub.set(value);    },     //=========================================================================     privates: {        registerChild: function(child) {            var children = this.children;            if (!children) {                this.children = children = {};            }            children[child.getId()] = child;        },                unregisterChild: function(child) {            var children = this.children;            // If we're destroying we'll be wiping this collection shortly, so             // just ignore it here             if (!this.destroying && children) {                delete children[child.getId()];            }        },         /**         * Get a record instance given a reference descriptor. Will ask         * the session if one exists.         * @param {String/Ext.Class} type The model type.         * @param {Object} id The model id.         * @return {Ext.data.Model} The model instance.         * @private         */         getRecord: function(type, id) {            var session = this.getSession(),                Model = type,                hasId = id !== undefined,                record;             if (session) {                if (hasId) {                    record = session.getRecord(type, id);                } else {                    record = session.createRecord(type);                }            } else {                if (!Model.$isClass) {                    Model = this.getSchema().getEntity(Model);                    //<debug>                     if (!Model) {                        Ext.raise('Invalid model name: ' + type);                    }                    //</debug>                 }                if (hasId) {                    record = Model.createWithId(id);                    record.load();                } else {                    record = new Model();                }            }            return record;        },         bindExpression: function (descriptor, callback, scope, options) {            var stub = this.getStub(descriptor);            return stub.bind(callback, scope, options);        },         applyScheduler: function (scheduler) {            if (scheduler && !scheduler.isInstance) {                scheduler = new Ext.util.Scheduler(scheduler);                scheduler.$owner = this;            }            return scheduler;        },         getScheduler: function () {            var me = this,                scheduler = me._scheduler,                parent;             if (!scheduler) {                if (!(parent = me.getParent())) {                    scheduler = new Ext.util.Scheduler({                        // See Session#scheduler                         preSort: 'kind,-depth'                    });                    scheduler.$owner = me;                } else {                    scheduler = parent.getScheduler();                }                 me.setScheduler(scheduler);            }             return scheduler;        },         /**         * This method looks up the `Stub` for a single bind descriptor.         * @param {String/Object} bindDescr The bind descriptor.         * @return {Ext.app.bind.AbstractStub} The `Stub` associated to the bind descriptor.         * @private         */        getStub: function (bindDescr) {            var root = this.getRoot();            return bindDescr ? root.getChild(bindDescr) : root;        },         collect: function() {            var me = this,                parent = me.getParent(),                task = me.collectTask;             if (parent) {                parent.collect();                return;            }             if (!task) {                task = me.collectTask = new Ext.util.DelayedTask(me.doCollect, me);            }             // Useful for testing             if (me.collectTimeout === 0) {                me.doCollect();            } else {                task.delay(me.collectTimeout);            }        },         doCollect: function() {            var children = this.children,                key;                        // We need to loop over the children first, since they may have link stubs             // that create bindings inside our VM. Attempt to clean them up first.             if (children) {                for (key in children) {                    children[key].doCollect();                }            }            this.getRoot().collect();        },         onBindDestroy: function(binding, fromChild) {            var me = this,                parent;             if (me.destroying) {                return;            }             if (!fromChild) {                delete me.bindings[binding.id];            }             parent = me.getParent();            if (parent) {                parent.onBindDestroy(binding, true);            } else {                me.collect();            }        },         //-------------------------------------------------------------------------         // Config         // <editor-fold>          applyData: function (newData, data) {            var me = this,                linkData, parent;             // Force any session to be invoked so we can access it             me.getSession();            if (!data) {                parent = me.getParent();                 /**                 * @property {Object} linkData                  * This object is used to hold the result of a linked value. This is done                 * so that the data object hasOwnProperty equates to whether or not this                 * property is owned by this instance or inherited.                 * @private                 * @readonly                 * @since 5.0.0                 */                me.linkData = linkData = parent ? Ext.Object.chain(parent.getData()) : {};                 /**                 * @property {Object} data                  * This object holds all of the properties of this `ViewModel`. It is                 * prototype chained to the `linkData` which is, in turn, prototype chained                 * to (if present) the `data` object of the parent `ViewModel`.                 * @private                 * @readonly                 * @since 5.0.0                 */                me.data = me._data = Ext.Object.chain(linkData);            }             if (newData && newData.constructor === Object) {                me.getRoot().set(newData);            }        },         applyParent: function(parent) {            if (parent) {                parent.registerChild(this);            }            return parent;        },                applyStores: function(stores) {            var me = this,                root = me.getRoot(),                key, cfg, storeBind, stub, listeners;                        me.storeInfo = {};            me.listenerScopeFn = function() {                return me.getView().getInheritedConfig('defaultListenerScope');            };            for (key in stores) {                cfg = stores[key];                if (cfg.isStore) {                    cfg.$wasInstance = true;                    me.setupStore(cfg, key);                    continue;                } else if (Ext.isString(cfg)) {                    cfg = {                        source: cfg                    };                } else {                    cfg = Ext.apply({}, cfg);                }                // Get rid of listeners so they don't get considered as a bind                 listeners = cfg.listeners;                delete cfg.listeners;                storeBind = me.bind(cfg, me.onStoreBind, me, {trackStatics: true});                if (storeBind.isStatic()) {                    // Everything is static, we don't need to wait, so remove the                     // binding because it will only fire the first time.                     storeBind.destroy();                    me.createStore(key, cfg, listeners);                } else {                    storeBind.$storeKey = key;                    storeBind.$listeners = listeners;                    stub = root.createStubChild(key);                    stub.setStore(storeBind);                }            }        },                onStoreBind: function(cfg, oldValue, binding) {            var info = this.storeInfo,                key = binding.$storeKey,                store = info[key],                proxy;             if (!store) {                this.createStore(key, cfg, binding.$listeners, binding);            } else {                cfg = Ext.merge({}, binding.pruneStaticKeys());                proxy = cfg.proxy;                delete cfg.type;                delete cfg.model;                delete cfg.fields;                delete cfg.proxy;                delete cfg.listeners;                                // TODO: possibly optimize this so we can figure out what has changed                 // instead of smashing the whole lot                 if (proxy) {                    delete proxy.reader;                    delete proxy.writer;                    store.getProxy().setConfig(proxy);                }                store.setConfig(cfg);            }        },         createStore: function(key, cfg, listeners, binding) {            var session = this.getSession(),                store;             cfg = Ext.apply({}, cfg);             if (cfg.session) {                cfg.session = session;            }            if (cfg.source) {                cfg.type = cfg.type || 'chained';            }             // Restore the listeners from applyStores here             cfg.listeners = listeners;            // Ensure events fired by ctor can find their target:             cfg.resolveListenerScope = this.listenerScopeFn;             store = Ext.Factory.store(cfg);            store.$binding = binding;             this.setupStore(store, key);        },         setupStore: function (store, key) {            // May have been given a store instance             store.resolveListenerScope = this.listenerScopeFn;            this.storeInfo[key] = store;            this.set(key, store);        },         applyFormulas: function (formulas) {            var me = this,                root = me.getRoot(),                name, stub;             me.getData(); // make sure our data is setup first              for (name in formulas) {                //<debug>                 if (name.indexOf('.') >= 0) {                    Ext.raise('Formula names cannot contain dots: ' + name);                }                //</debug>                                 // Force a stub to be created                 root.createStubChild(name);                 stub = me.getStub(name);                stub.setFormula(formulas[name]);            }            return formulas;        },         applyLinks: function (links) {            for (var link in links) {                this.linkTo(link, links[link]);            }        },         applySchema: function (schema) {            return Ext.data.schema.Schema.get(schema);        },         applyRoot: function () {            var root = new Ext.app.bind.RootStub(this),                parent = this.getParent();             if (parent) {                // We are assigning the root of a child VM such that its bindings will be                 // pre-sorted after the bindings of the parent VM.                 root.depth = parent.getRoot().depth - 1000;            }             return root;        },         getFormulaFn: function(data) {            var me = this,                fn = me.formulaFn;             if (!fn) {                fn = me.formulaFn = function(name) {                    // Note that the `this` pointer here is the view model because                     // the VM calls it in the VM scope.                     return me.$formulaData[name];                };            }            me.$formulaData = data;            return fn;        }         // </editor-fold>     }});