1 sc_require('system/object');
  2 
  3 /**
  4   @class
  5   @private
  6 
  7   SC._PropertyChain is used as the bookkeeping system for notifying the KVO
  8   system of changes to computed properties that contains paths as dependent
  9   keys.
 10 
 11   Each instance of SC._PropertyChain serves as a node in a linked list. One node
 12   is created for each property in the path, and stores a reference to the name
 13   of the property and the object to which it belongs. If that property changes,
 14   the SC._PropertyChain instance notifies its associated computed property to
 15   invalidate, then rebuilds the chain with the new value.
 16 
 17   To create a new chain, call SC._PropertyChain.createChain() with the target,
 18   path, and property to invalidate if any of the objects in the path change.
 19 
 20   For example, if you called createChain() with 'foo.bar.baz', it would
 21   create a linked list like this:
 22 
 23    ---------------------     ---------------------     ---------------------
 24   | property:     'foo' |   | property:     'bar' |   | property:     'baz' |
 25   | nextProperty: 'bar' |   | nextProperty: 'baz' |   | nextProperty: undef |
 26   | next:           ------->| next:           ------->| next:     undefined |
 27    ---------------------     ---------------------     ---------------------
 28 
 29   @extends SC.Object
 30   @since SproutCore 1.5
 31 */
 32 
 33 SC._PropertyChain = SC.Object.extend(
 34 /** @scope SC.Object.prototype */ {
 35   /**
 36     The object represented by this node in the chain.
 37 
 38     @type Object
 39   */
 40   object: null,
 41 
 42   /**
 43     The key on the previous object in the chain that contains the object
 44     represented by this node in the chain.
 45 
 46     @type String
 47   */
 48   property: null,
 49 
 50   /**
 51     The target object. This is the object passed to createChain(), and the
 52     object which contains the +toInvalidate+ property that will be invalidated
 53     if +property+ changes.
 54 
 55     @type Object
 56   */
 57   target: null,
 58 
 59   /**
 60     The property of +target+ to invalidate when +property+ changes.
 61 
 62     @type String
 63   */
 64   toInvalidate: null,
 65 
 66   /**
 67     The property key on +object+ that contains the object represented by the
 68     next node in the chain.
 69 
 70     @type String
 71   */
 72   nextProperty: null,
 73 
 74   /**
 75     Registers this segment of the chain with the object it represents.
 76 
 77     This should be called with the object represented by the previous node in
 78     the chain as the first parameter. If no previous object is provided, it will
 79     assume it is the root node in the chain and treat the target as the previous
 80     object.
 81 
 82     @param {Object} [newObject] The object in the chain to hook to.
 83   */
 84   activate: function(newObject) {
 85     var curObject = this.get('object'),
 86         property  = this.get('property'),
 87         nextObject;
 88 
 89     // If no parameter is passed, assume we are the root in the chain
 90     // and look up property relative to the target, since dependent key
 91     // paths are always relative.
 92     if (!newObject) { newObject = this.get('target'); }
 93 
 94     if (curObject && curObject!==newObject) {
 95       this.deactivate();
 96     }
 97     this.set('object', newObject);
 98 
 99     // In the special case of @each, we treat the enumerable as the next
100     // property so just skip registering it
101     if (newObject && property!=='@each') {
102       newObject.registerDependentKeyWithChain(property, this);
103     }
104 
105     // now - lookup the object for the next one...
106     if (this.next) {
107       nextObject = newObject ? newObject.get(property) : undefined;
108       this.next.activate(nextObject);
109     }
110 
111     return this;
112   },
113 
114   /**
115     Removes this segment of the chain from the object it represents. This is
116     usually called when the object represented by the previous segment in the
117     chain changes.
118   */
119   deactivate: function() {
120     var object   = this.get('object'),
121         property = this.get('property');
122 
123     // If the chain element is not associated with an object,
124     // we don't need to deactivate anything.
125     if (object) object.removeDependentKeyWithChain(property, this);
126     if (this.next) this.next.deactivate();
127     return this;
128   },
129 
130   /**
131     Invalidates the +toInvalidate+ property of the +target+ object.
132   */
133   notifyPropertyDidChange: function() {
134     var target       = this.get('target'),
135         toInvalidate = this.get('toInvalidate'),
136         curObj, newObj;
137 
138     // Tell the target of the chain to invalidate the property
139     // that depends on this element of the chain
140     target.propertyDidChange(toInvalidate);
141 
142     // If there are more dependent keys in the chain, we need
143     // to invalidate them and set them up again.
144     if (this.next) {
145       // Get the new value of the object associated with this node to pass to
146       // activate().
147       curObj = this.get('object');
148       newObj = curObj.get(this.get('property'));
149 
150       this.next.activate(newObj); // reactivate down the line
151     }
152   }
153 
154   // @if (debug)
155   ,
156   /**
157     Returns a string representation of the chain segment.
158 
159     @returns {String}
160   */
161   toString: function() {
162     return "SC._PropertyChain(target: %@, property: %@)".fmt(
163       this.get('target'), this.get('property'));
164   }
165   // @endif
166 });
167 
168 SC._PropertyChain.createChain = function(path, target, toInvalidate) {
169   var parts = path.split('.');
170   var len = parts.length,
171       i   = 1;
172 
173   var root = SC._PropertyChain.create({
174     property:     parts[0],
175     target:       target,
176     toInvalidate: toInvalidate,
177     nextProperty: parts[1]
178   });
179 
180 
181   root.set('length', len);
182   var tail = root;
183 
184   while(--len >= 1) {
185     tail = tail.next = SC._PropertyChain.create({
186       property:     parts[i],
187       target:       target,
188       toInvalidate: toInvalidate,
189       nextProperty: parts[++i]
190     });
191 
192     tail.set('length', len);
193   }
194 
195   return root;
196 };
197