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