ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
269 lines (262 loc) • 9.74 kB
JavaScript
//*@public
/**
These properties provide the public API for using [enyo.Binding](#enyo.Binding)
with any [enyo.Object](#enyo.Object) kind or subclass. This support requires
the [ObserverSupport](#enyo/source/kernel/mixins/ObserverSupport.js) and
[ComputedSupport](#enyo/source/kernel/mixins/ComputedSupport.js) mixins to
have been applied. This API is available on _all_ instances and subkinds of
_enyo.Object_; thus, it does not need to be added as a mixin to any other kind.
*/
enyo.BindingSupport = {
name: "BindingSupport",
/**
While the binding kind may be overloaded on a per-binding basis
for objects that intend to use a custom kind for all of their
bindings, it may also be set here:
`defaultBindingKind`
*/
/**
Set this to an array of binding declarations that will be created
when the object is instantiated. Post-construction, this array will
contain a reference to all available bindings on the instance of the kind:
`bindings`
*/
/**
Set this to a hash of the available options to supply to all bindings
created by this object. The defaults will only be used if the specified
properties are not found in the binding definition:
`bindingDefaults`
*/
/**
To create a binding on its own (not with the _bindings_ array for the kind),
pass the properties to this method. It accepts multiple hashes of properties
to apply to the binding. The binding will have its owner set to this
instance and a reference to the newly created binding will be returned. When
this instance is destroyed, all the bindings that it owns will also be
destroyed. If this method is called too early (i.e., before bindings have
been fully initialized), it will add the properties to the initialization
queue and return _undefined_. If no kind is explicitly defined in the
binding properties, it will be assigned as either the kind's
_defaultBindingKind_ or the global _enyo.defaultBindingKind_.
*/
binding: function () {
var defs = enyo.toArray(arguments),
props = enyo.mixin(defs),
bs = this.bindings || (this.bindings = []),
bd;
props.owner = props.owner || this;
props.kind = props.kind || this.defaultBindingKind || enyo.defaultBindingKind;
if (this.bindingSupportInitialized === false) {
bs.push(props);
} else {
// we only want to resolve the kind if it isn't already
// the correct constructor -- note this forces the kind
// to be resolved at that time
if (!enyo.isFunction(props.kind)) {
props.kind = enyo.getPath(props.kind);
}
bs.push((bd=new props.kind(props)));
}
return bd;
},
/**
Usually called when the object's _destroy()_ method is executed, but may be
called at any time to properly clean up the bindings associated with this
object (i.e., any bindings that have their _owner_ property set to this
object).
This method does not remove bindings that originated from another object
but are currently bound to a property on this object.
If desired, one may pass in an array of bindings, in which case only the
bindings specified in the array will be destroyed.
*/
clearBindings: function (subset) {
var bs = subset || this.bindings;
if (!bs) { return; }
for (var i=0, b; (b=bs[i]); ++i) {
b.destroy();
}
},
/**
Calls the _refresh()_ method on the bindings associated with this object, or
on a passed-in array of bindings.
Differs from _rebuildBindings()_ in that, instead of rediscovering the
source and target of each binding, it remembers them from the most recent
setup.
In most scenarios, this method will be called automatically, with no need
for explicit calls from the developer.
*/
refreshBindings: function (subset) {
var bs = subset || this.bindings;
if (!bs) { return; }
for (var i=0, b; (b=bs[i]); ++i) {
b.refresh();
}
},
/**
Calls the _rebuild()_ method on the bindings associated with this object, or
on a passed-in array of bindings.
Differs from _refreshBindings()_ in that it forces the source and target of
each binding to be rediscovered using the specified paths, rather than
remembering them from a previous setup.
In most scenarios, this method will be called automatically, with no need
for explicit calls from the developer.
*/
rebuildBindings: function (subset) {
var bs = subset || this.bindings;
if (!bs) { return; }
for (var i=0, b; (b=bs[i]); ++i) {
b.rebuild();
}
},
/**
This method is typically not called directly, but is called by the
binding when it is destroyed. It accepts a single binding as its
parameter; the binding is removed from the _bindings_ array if it
exists there. This method does not destroy the binding or dereference
its _owner_ property.
*/
removeBinding: function (binding) {
var bs = this.bindings;
if (binding && bs && bs.length) {
var i = enyo.indexOf(binding, bs);
if (i > -1) {
bs.splice(i, 1);
}
}
},
//*@public
/**
This method is exposed for overloading purposes if necessary.
It is used to initialize bindings on an instance when it is
created. When bindings are initialized, they attempt to connect
(if the _autoConnect_ flag is true, which it is by default). If they
cannot connect, they will (if possible) register to be notified when that
object becomes available, and will connect and synchronize then.
*/
initBindings: function () {
var i, b;
if (false === this.bindingSupportInitialized) {
this.bindingSupportInitialized = undefined;
var bs = this.bindings;
if (!bs) { return; }
// we will now reuse the property `bindings` with the actual binding
// references
this.bindings = [];
for (i=0; (b=bs[i]); ++i) {
this.binding(b);
}
}
},
//*@protected
constructed: enyo.inherit(function (sup) {
return function () {
// now we go ahead and create the bindings, knowing that they
// will register for missing targets/sources if they become
// available later
if (this.bindings) {
this.initBindings();
} else {
// to ensure that bindings will be properly registered later
// we set this flag here
this.bindingSupportInitialized = undefined;
}
sup.apply(this, arguments);
};
}),
destroy: enyo.inherit(function (sup) {
return function () {
// destroy all bindings that belong to us
var bs = this.bindings;
if (bs) {
for (var i=bs.length-1, b; (b=bs[i]); --i) {
b.destroy();
}
}
sup.apply(this, arguments);
};
}),
/**
Attempt to identify when we have successfully been able to register the end of
the chain for a binding and synchronize only then.
*/
addObserver: enyo.inherit(function (sup) {
return function (path, fn, ctx) {
// the actual returned registered observer from the binding
var ret = sup.apply(this, arguments),
t = this,
c;
// all bindings add a reference of themselves to their observer methods
if (fn.binding) {
// expect no periods or one if the initial character is '$' because
// of its special nature (it will be forced to register this way)
// it is important to note the difference in the final test here that if the
// current path being executed is from the source and the condition is the '$'
// we do not retrieve the final object - regardless as we already have the correct
// root but for target we need to attempt to retrieve that root
if (((c=enyo.lastIndexOf(".", path)) === -1) || (c === 1 && path[0] == "$" && (fn.bindingProp == "source" || (t=this.get(path))))) {
// we tell the binding which of the methods it was registering as
// an observer have successfully completed and it will automatically
// synchronize if it is supposed to and only when both ends are complete
fn.binding.registered(fn.bindingProp, t);
}
// either way we want to ensure that the reference to the binding
// remains on the returned method even if it isn't the original
ret.binding = fn.binding;
}
return ret;
};
}),
/**
Flag that indicates whether bindings have been initialized for this object.
It is used as an explicit _false_ test because it is removed from the object
instance once initialized, to reduce object clutter.
*/
bindingSupportInitialized: false,
bindingSyncAllowed: true
/**
Meta-properties used:
`bindingSyncQueue`
*/
};
//*@protected
(function (enyo) {
var fn = enyo.concatHandler;
enyo.concatHandler = function (ctor, props) {
// call the original
fn.apply(this, arguments);
if (props.bindings) {
// now we need to setup our bindings appropriately
var p = ctor.prototype || ctor,
k = props.defaultBindingKind || enyo.defaultBindingKind,
d = props.bindingDefaults;
for (var i=0, b; (b=props.bindings[i]); ++i) {
if (d) { enyo.mixin(b, d, {ignore: true}); }
b.kind = b.kind || k;
}
p.bindings = p.bindings? p.bindings.concat(props.bindings): props.bindings;
delete props.bindings;
}
};
})(enyo);
//*@public
/**
BindingSupport is available on instances of _enyo.Object_, but it is necessary
to overload methods that aren't available on _enyo.Object_ but are on
_enyo.Component_, so it is added as additional functionality.
*/
enyo.ComponentBindingSupport = {
name: "ComponentBindingSupport",
//*@protected
/**
There is a special property, *bindingTransformOwner*, that needs to be
chained down into children to shortcut bindings work to find transforms
for inlined bindings -- their owner is the component they are nested on
but the transform will most likely exist on the instance owner.
*/
adjustComponentProps: enyo.inherit(function (sup) {
return function (props) {
sup.apply(this, arguments);
props.bindingTransformOwner = props.bindingTransformOwner || this.getInstanceOwner();
};
})
};