/**
* 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();
}
}
});