// ========================================================================== // Project: SproutCore
Costello - Property Observing Library // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ==========================================================================
// .….….….….….….….….….….….….….….….….….… // CHAIN OBSERVER //
// This is a private class used by the observable mixin to support chained // properties.
// ChainObservers are used to automatically monitor a property several // layers deep. // org.plan.name = SC
._ChainObserver.create({ // target: this, property: 'org', // next: SC
._ChainObserver.create({ // property: 'plan', // next: SC
._ChainObserver.create({ // property: 'name', func: myFunc // }) // }) // }) // SC
._ChainObserver = function (property, root) {
this.property = property; this.root = root || this;
};
/** @private
This is the primary entry point. Configures the chain. @param {String} path The property path for the chain. Ex. 'propA.propB.propC.@each.propD' */
SC
._ChainObserver.createChain = function (rootObject, path, target, method, context) {
// First we create the chain. var parts = path.split('.'), // ex. ['propA', 'propB', '@each', 'propC'] root = new SC._ChainObserver(parts[0]), // ex. _ChainObserver({ property: 'propA' }) tail = root; for (var i = 1, len = parts.length; i < len; i++) { tail = tail.next = new SC._ChainObserver(parts[i], root); } var tails = root.tails = [tail]; // ex. [_ChainObserver({ property: 'propC' })] // Now root has the first observer and tail has the last one. // Feed the rootObject into the front to setup the chain... // do this BEFORE we set the target/method so they will not be triggered. root.objectDidChange(rootObject); tails.forEach(function (tail) { // Finally, set the target/method on the tail so that future changes will trigger. tail.target = target; tail.method = method; tail.context = context; }); // no need to hold onto references to the tails; if the underlying // objects go away, let them get garbage collected root.tails = null; // and return the root to save return root;
};
SC
._ChainObserver.prototype = {
isChainObserver: true, // the object this instance is observing object: null, // the property on the object this link is observing. property: null, // if not null, this is the next link in the chain. Whenever the // current property changes, the next observer will be notified. next: null, root: null, // if not null, this is the final target observer. target: null, // if not null, this is the final target method method: null, // an accessor method that traverses the list and finds the tail tail: function () { if (this._tail) { return this._tail; } var tail = this; while (tail.next) { tail = tail.next; } this._tail = tail; return tail; }, // invoked when the source object changes. removes observer on old // object, sets up new observer, if needed. objectDidChange: function (newObject) { if (newObject === this.object) return; // nothing to do. // if an old object, remove observer on it. if (this.object) { if (this.property === '@each' && this.object._removeContentObserver) { this.object._removeContentObserver(this); } else if (this.object.removeObserver) { this.object.removeObserver(this.property, this, this.propertyDidChange); } } // if a new object, add observer on it... this.object = newObject; // when [].propName is used, we will want to set up observers on each item // added to the Enumerable, and remove them when the item is removed from // the Enumerable. // // In this case, we invoke addEnumerableObserver, which handles setting up // and tearing down observers as items are added and removed from the // Enumerable. if (this.property === '@each' && this.next) { if (this.object && this.object._addContentObserver) { this.object._addContentObserver(this); } } else { if (this.object && this.object.addObserver) { this.object.addObserver(this.property, this, this.propertyDidChange); } // now, notify myself that my property value has probably changed. this.propertyDidChange(); } }, // the observer method invoked when the observed property changes. propertyDidChange: function () { // get the new value var object = this.object; var property = this.property; var value = (object && object.get) ? object.get(property) : null; // if we have a next object in the chain, notify it that its object // did change... if (this.next) { this.next.objectDidChange(value); } // if we have a target/method, call it. var target = this.target, method = this.method, context = this.context; if (target && method) { var rev = object ? object.propertyRevision : null; if (context) { method.call(target, object, property, value, context, rev); } else { method.call(target, object, property, value, rev); } } }, // teardown the chain... destroyChain: function () { // remove observer var obj = this.object; if (obj) { if (this.property === '@each' && this.next && obj._removeContentObserver) { obj._removeContentObserver(this); } if (obj.removeObserver) { obj.removeObserver(this.property, this, this.propertyDidChange); } } // destroy next item in chain if (this.next) this.next.destroyChain(); // and clear left overs... this.next = this.target = this.method = this.object = this.context = null; return null; }
};