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