/** * This class manages bindings for a `Session` or `ViewModel`. * @private */Ext.define('Ext.app.bind.AbstractStub', { extend: 'Ext.util.Schedulable', requires: [ 'Ext.app.bind.Binding' ], children: null, depth: 0, generation: 1, kind: 10, parent: null, constructor: function(owner, name) { var me = this; /** * @property {Ext.data.Session/Ext.app.ViewModel} owner * This property is set at creation of ths stub and should not be changed. * @readonly */ me.owner = owner; me.name = name; me.callParent(); }, destroy: function() { var me = this, children = me.children, bindings = me.bindings, len, i, key; if (bindings) { for (i = 0, len = bindings.length; i < len; ++i) { bindings[i].destroy(true); } } for (key in children) { children[key].destroy(); } if (me.scheduled) { me.unschedule(); } me.callParent(); }, add: function(child) { var me = this; (me.children || (me.children = {}))[child.name] = child; child.depth = me.depth + 1; child.parent = me; }, getChild: function(path) { var pathArray = Ext.isString(path) ? path.split('.') : path; if (pathArray && pathArray.length) { return this.descend(pathArray, 0); } return this; }, getFullName: function() { var me = this, name = me.fullName, parent = me.parent, s; if (!name) { name = me.name || me.id; if (parent && (s = parent.getFullName())) { name = ((s.charAt(s.length - 1) !== ':') ? s + '.' : s) + name; } me.fullName = name; } return name; }, getSession: function() { var owner = this.owner; return owner.isSession ? owner : owner.getSession(); }, bind: function(callback, scope, options) { var me = this, binding = new Ext.app.bind.Binding(me, callback, scope, options), bindings = (me.bindings || (me.bindings = [])); binding.depth = me.depth; bindings.push(binding); return binding; }, getValue: function() { return this.isAvailable() ? this.getRawValue() : null; }, graft: function(replacement) { var me = this, bindings = me.bindings, name = me.name, i; // Clear these so that when we call destroy we won't damage anything: me.parent = me.bindings = null; me.destroy(); // we may be scheduled replacement.depth = me.depth; replacement.bindings = bindings; replacement.generation = me.generation + 1; replacement.name = name; replacement.id = me.id; replacement.path = me.path; // Now for the fun part... if (bindings) { for (i = bindings.length; i-- > 0;) { bindings[i].stub = replacement; } } return replacement; }, isDescendantOf: function(item) { var parent; for (parent = this; parent = parent.parent;) { // eslint-disable-line no-cond-assign if (parent === item) { return true; } } return false; }, isAvailable: function() { return true; }, isLoading: function() { return false; }, onSchedule: function() { var i, len, binding, bindings, p; // When a stub changes, say "foo.bar.baz" we may need to notify bindings on our // parents "foo.bar" and "foo", This is true especially when these are targets of // links. To economize on this we require that bindings that want to be notified // of changes to sub-properties of their target set the "deep" property to true. for (p = this.parent; p; p = p.parent) { bindings = p.bindings; if (bindings) { for (i = 0, len = bindings.length; i < len; ++i) { binding = bindings[i]; if (binding.deep && !binding.scheduled) { binding.schedule(); } } } } }, react: function() { var bindings = this.bindings, binding, i, len; if (bindings) { for (i = 0, len = bindings.length; i < len; ++i) { binding = bindings[i]; if (!binding.scheduled) { binding.schedule(); } } } }, unbind: function(binding) { var bindings = this.bindings; if (bindings && bindings.length) { Ext.Array.remove(bindings, binding); } }, privates: { collect: function() { var children = this.children, bindings = this.bindings, totalCount = 0, count = 0, child, key; if (children) { for (key in children) { child = children[key]; count = child.collect(); if (count === 0) { // The child (and any deep children) have no bindings, // so we can consider it a dead node. child.destroy(); delete children[key]; } totalCount += count; } } if (bindings) { totalCount += bindings.length; } return totalCount; }, getScheduler: function() { var owner = this.owner; return owner && owner.getScheduler(); }, sort: function() { var parent = this.parent; if (parent) { // We sort our parent first because if it is something like a link we need // it to determine the value of the root-level property before we can dot // our way into it. This is especially important for formulas that might // throw errors if the links have not published results before they run. this.scheduler.sortItem(parent); } // Schedulable#sort === emptyFn // me.callParent(); } }});