1 // ========================================================================== 2 // Project: SproutCore Costello - Property Observing Library 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 8 sc_require('mixins/observable'); 9 sc_require('system/set'); 10 11 // ........................................................................ 12 // OBSERVER QUEUE 13 // 14 // This queue is used to hold observers when the object you tried to observe 15 // does not exist yet. This queue is flushed just before any property 16 // notification is sent. 17 18 /** 19 @namespace 20 21 The private ObserverQueue is used to maintain a set of pending observers. 22 This allows you to setup an observer on an object before the object exists. 23 24 Whenever the observer fires, the queue will be flushed to connect any 25 pending observers. 26 27 @private 28 @since SproutCore 1.0 29 */ 30 SC.Observers = { 31 32 queue: [], 33 34 /** 35 @private 36 37 Attempt to add the named observer. If the observer cannot be found, put 38 it into a queue for later. 39 */ 40 addObserver: function(propertyPath, target, method, pathRoot) { 41 var tuple ; 42 43 // try to get the tuple for this. 44 if (typeof propertyPath === "string") { 45 tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ; 46 } else { 47 tuple = propertyPath; 48 } 49 50 // if a tuple was found and is observable, add the observer immediately... 51 if (tuple && tuple[0].addObserver) { 52 tuple[0].addObserver(tuple[1],target, method) ; 53 54 // otherwise, save this in the queue. 55 } else { 56 this.queue.push([propertyPath, target, method, pathRoot]) ; 57 } 58 }, 59 60 /** 61 @private 62 63 Remove the observer. If it is already in the queue, remove it. Also 64 if already found on the object, remove that. 65 */ 66 removeObserver: function(propertyPath, target, method, pathRoot) { 67 var idx, queue, tuple, item; 68 69 tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ; 70 if (tuple) { 71 tuple[0].removeObserver(tuple[1], target, method); 72 } 73 //@if(debug) 74 // Add some developer support indicating that the observer was not removed. 75 else { 76 SC.warn("Developer Warning: Attempted to remove observer for propertyPath %@ with root %@ and failed. This may be because an object that the path refers to is no longer available.".fmt(propertyPath, pathRoot)); 77 } 78 //@endif 79 80 // tests show that the fastest way is to create a new array. On Safari, 81 // it is fastest to set to null then loop over again to collapse, but for all other browsers 82 // it is not. Plus, this code shouldn't get hit very often anyway (it may not ever get hit 83 // for some apps). 84 idx = this.queue.length; 85 queue = this.queue; 86 87 var newQueue; 88 while(--idx >= 0) { 89 item = queue[idx]; 90 91 if (item[0] !== propertyPath || item[1] !== target || item[2] !== method || item[3] !== pathRoot) { 92 if (!newQueue) newQueue = []; 93 newQueue.push(item); 94 } 95 } 96 97 // even though performance probably won't be a problem, we are defensive about memory alloc. 98 this.queue = newQueue || this.queue; 99 }, 100 101 /** 102 @private 103 104 Range Observers register here to indicate that they may potentially 105 need to start observing. 106 */ 107 addPendingRangeObserver: function(observer) { 108 var ro = this.rangeObservers; 109 if (!ro) ro = this.rangeObservers = SC.CoreSet.create(); 110 ro.add(observer); 111 return this ; 112 }, 113 114 _TMP_OUT: [], 115 116 /** 117 Flush the queue. Attempt to add any saved observers. 118 */ 119 flush: function(object) { 120 121 // flush any observers that we tried to setup but didn't have a path yet 122 var oldQueue = this.queue, i, 123 queueLen = oldQueue.length; 124 125 if (oldQueue && queueLen > 0) { 126 var newQueue = (this.queue = []) ; 127 128 for (i=0; i<queueLen; i++ ) { 129 var item = oldQueue[i]; 130 if ( !item ) continue; 131 132 var tuple = SC.tupleForPropertyPath( item[0], item[3] ); 133 // check if object is observable (yet) before adding an observer 134 if( tuple && tuple[0].addObserver ) { 135 tuple[0].addObserver( tuple[1], item[1], item[2] ); 136 } else { 137 newQueue.push( item ); 138 } 139 } 140 } 141 142 // if this object needsRangeObserver then see if any pending range 143 // observers need it. 144 if ( object._kvo_needsRangeObserver ) { 145 var set = this.rangeObservers, 146 len = set ? set.get('length') : 0, 147 out = this._TMP_OUT, 148 ro; 149 150 for ( i=0; i<len; i++ ) { 151 ro = set[i]; // get the range observer 152 if ( ro.setupPending(object) ) { 153 out.push(ro); // save to remove later 154 } 155 } 156 157 // remove any that have setup 158 if ( out.length > 0 ) set.removeEach(out); 159 out.length = 0; // reset 160 object._kvo_needsRangeObserver = false ; 161 } 162 163 }, 164 165 /** @private */ 166 isObservingSuspended: 0, 167 168 _pending: SC.CoreSet.create(), 169 170 /** @private */ 171 objectHasPendingChanges: function(obj) { 172 this._pending.add(obj) ; // save for later 173 }, 174 175 /** @private */ 176 // temporarily suspends all property change notifications. 177 suspendPropertyObserving: function() { 178 this.isObservingSuspended++ ; 179 }, 180 181 // resume change notifications. This will call notifications to be 182 // delivered for all pending objects. 183 /** @private */ 184 resumePropertyObserving: function() { 185 var pending ; 186 if(--this.isObservingSuspended <= 0) { 187 pending = this._pending ; 188 this._pending = SC.CoreSet.create() ; 189 190 var idx, len = pending.length; 191 for(idx=0;idx<len;idx++) { 192 pending[idx]._notifyPropertyObservers() ; 193 } 194 pending.clear(); 195 pending = null ; 196 } 197 } 198 199 } ; 200