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('ext/function');
  9 sc_require('private/observer_set');
 10 sc_require('private/chain_observer');
 11 
 12 //@if(debug)
 13 /**
 14   Set to YES to have all observing activity logged to the console.
 15 
 16   This is only available in debug mode.
 17 
 18   @type Boolean
 19 */
 20 SC.LOG_OBSERVERS = false;
 21 //@endif
 22 
 23 SC.OBSERVES_HANDLER_ADD = 0;
 24 SC.OBSERVES_HANDLER_REMOVE = 1;
 25 
 26 /**
 27   @class
 28 
 29   Key-Value-Observing (KVO) simply allows one object to observe changes to a
 30   property on another object. It is one of the fundamental ways that models,
 31   controllers and views communicate with each other in a SproutCore
 32   application.  Any object that has this module applied to it can be used in
 33   KVO-operations.
 34 
 35   This module is applied automatically to all objects that inherit from
 36   SC.Object, which includes most objects bundled with the SproutCore
 37   framework.  You will not generally apply this module to classes yourself,
 38   but you will use the features provided by this module frequently, so it is
 39   important to understand how to use it.
 40 
 41   Enabling Key Value Observing
 42   ---
 43 
 44   With KVO, you can write functions that will be called automatically whenever
 45   a property on a particular object changes.  You can use this feature to
 46   reduce the amount of "glue code" that you often write to tie the various
 47   parts of your application together.
 48 
 49   To use KVO, just use the KVO-aware methods get() and set() to access
 50   properties instead of accessing properties directly.  Instead of writing:
 51 
 52         var aName = contact.firstName;
 53         contact.firstName = 'Charles';
 54 
 55   use:
 56 
 57         var aName = contact.get('firstName');
 58         contact.set('firstName', 'Charles');
 59 
 60   get() and set() work just like the normal "dot operators" provided by
 61   JavaScript but they provide you with much more power, including not only
 62   observing but computed properties as well.
 63 
 64   Observing Property Changes
 65   ---
 66 
 67   You typically observe property changes simply by adding the observes()
 68   call to the end of your method declarations in classes that you write.  For
 69   example:
 70 
 71         SC.Object.create({
 72           valueObserver: function () {
 73             // Executes whenever the "Value" property changes
 74           }.observes('value')
 75         });
 76 
 77   Although this is the most common way to add an observer, this capability is
 78   actually built into the SC.Object class on top of two methods defined in
 79   this mixin called addObserver() and removeObserver().  You can use these two
 80   methods to add and remove observers yourself if you need to do so at run
 81   time.
 82 
 83   To add an observer for a property, just call:
 84 
 85         object.addObserver('propertyKey', targetObject, targetAction);
 86 
 87   This will call the 'targetAction' method on the targetObject to be called
 88   whenever the value of the propertyKey changes.
 89 
 90   Observer Parameters
 91   ---
 92 
 93   An observer function typically does not need to accept any parameters,
 94   however you can accept certain arguments when writing generic observers.
 95   An observer function can have the following arguments:
 96 
 97         propertyObserver(target, key, value, revision);
 98 
 99   - *target* - This is the object whose value changed.  Usually this.
100   - *key* - The key of the value that changed
101   - *value* - this property is no longer used.  It will always be null
102   - *revision* - this is the revision of the target object
103 
104   Implementing Manual Change Notifications
105   ---
106 
107   Sometimes you may want to control the rate at which notifications for
108   a property are delivered, for example by checking first to make sure
109   that the value has changed.
110 
111   To do this, you need to implement a computed property for the property
112   you want to change and override automaticallyNotifiesObserversFor().
113 
114   The example below will only notify if the "balance" property value actually
115   changes:
116 
117 
118         automaticallyNotifiesObserversFor: function (key) {
119           return (key === 'balance') ? NO : sc_super();
120         },
121 
122         balance: function (key, value) {
123           var balance = this._balance;
124           if ((value !== undefined) && (balance !== value)) {
125             this.propertyWillChange(key);
126             balance = this._balance = value;
127             this.propertyDidChange(key);
128           }
129           return balance;
130         }
131 
132 
133   Implementation Details
134   ---
135 
136   Internally, SproutCore keeps track of observable information by adding a
137   number of properties to the object adopting the observable.  All of these
138   properties begin with "_kvo_" to separate them from the rest of your object.
139 
140   @since SproutCore 1.0
141 */
142 SC.Observable = /** @scope SC.Observable.prototype */ {
143 
144   //@if(debug)
145   /* BEGIN DEBUG ONLY PROPERTIES AND METHODS */
146 
147   /**
148     Allows you to inspect a property for changes. Whenever the named property
149     changes, a log will be printed to the console. This (along with removeProbe)
150     are convenience methods meant for debugging purposes.
151 
152     @param {String} key The name of the property you want probed for changes
153   */
154   addProbe: function (key) { this.addObserver(key, SC.logChange); },
155 
156   /**
157     Stops a running probe from observing changes to the observer.
158 
159     @param {String} key The name of the property you want probed for changes
160   */
161   removeProbe: function (key) { this.removeObserver(key, SC.logChange); },
162 
163   /**
164     Logs the named properties to the console.
165 
166     @param {String...} propertyNames one or more property names
167   */
168   logProperty: function () {
169     var props = SC.$A(arguments),
170         prop, propsLen, idx;
171     for (idx = 0, propsLen = props.length; idx < propsLen; idx++) {
172       prop = props[idx];
173       console.log('%@:%@: '.fmt(SC.guidFor(this), prop), this.get(prop));
174     }
175   },
176 
177   /* END DEBUG ONLY PROPERTIES AND METHODS */
178   //@endif
179 
180   /** @private Property cache. */
181   _kvo_cache: null,
182 
183   /** @private Whether properties of the object will be cacheable. Becomes true if any one computed property is .cacheable() */
184   _kvo_cacheable: false,
185 
186   /** @private Cache of dependepents for a property. */
187   _kvo_cachedep: null,
188 
189   /** @private */
190   _kvo_changeLevel: 0,
191 
192   /** @private */
193   _kvo_changes: null,
194 
195   /** @private */
196   _kvo_cloned: null,
197 
198   /** @private */
199   _kvo_revision: 0,
200 
201   /** @private */
202   _observableInited: false,
203 
204   /**
205     Walk like that ol' duck
206 
207     @type Boolean
208   */
209   isObservable: YES,
210 
211   /**
212     Determines whether observers should be automatically notified of changes
213     to a key.
214 
215     If you are manually implementing change notifications for a property, you
216     can override this method to return NO for properties you do not want the
217     observing system to automatically notify for.
218 
219     The default implementation always returns YES.
220 
221     @param {String} key the key that is changing
222     @returns {Boolean} YES if automatic notification should occur.
223   */
224   automaticallyNotifiesObserversFor: function (key) {
225     return YES;
226   },
227 
228   // ..........................................
229   // PROPERTIES
230   //
231   // Use these methods to get/set properties.  This will handle observing
232   // notifications as well as allowing you to define functions that can be
233   // used as properties.
234 
235   /**
236     Retrieves the value of key from the object.
237 
238     This method is generally very similar to using object[key] or object.key,
239     however it supports both computed properties and the unknownProperty
240     handler.
241 
242     Computed Properties
243     ---
244 
245     Computed properties are methods defined with the property() modifier
246     declared at the end, such as:
247 
248           fullName: function () {
249             return this.getEach('firstName', 'lastName').compact().join(' ');
250           }.property('firstName', 'lastName')
251 
252     When you call get() on a computed property, the property function will be
253     called and the return value will be returned instead of the function
254     itself.
255 
256     Unknown Properties
257     ---
258 
259     Likewise, if you try to call get() on a property whose values is
260     undefined, the unknownProperty() method will be called on the object.
261     If this method returns any value other than undefined, it will be returned
262     instead.  This allows you to implement "virtual" properties that are
263     not defined upfront.
264 
265     @param {String} key the property to retrieve
266     @returns {Object} the property value or undefined.
267 
268   */
269   get: function (key) {
270     var ret = this[key], cache;
271     if (ret === undefined) {
272       return this.unknownProperty(key);
273     } else if (ret && ret.isProperty) {
274       if (ret.isCacheable) {
275         cache = this._kvo_cache;
276         if (!cache) cache = this._kvo_cache = {};
277 
278         return (cache[ret.cacheKey] !== undefined) ? cache[ret.cacheKey] : (cache[ret.cacheKey] = ret.call(this, key));
279       } else return ret.call(this, key);
280     } else return ret;
281   },
282 
283   /**
284     Sets the key equal to value.
285 
286     This method is generally very similar to calling object[key] = value or
287     object.key = value, except that it provides support for computed
288     properties, the unknownProperty() method and property observers.
289 
290     Computed Properties
291     ---
292 
293     If you try to set a value on a key that has a computed property handler
294     defined (see the get() method for an example), then set() will call
295     that method, passing both the value and key instead of simply changing
296     the value itself.  This is useful for those times when you need to
297     implement a property that is composed of one or more member
298     properties.
299 
300     Unknown Properties
301     ---
302 
303     If you try to set a value on a key that is undefined in the target
304     object, then the unknownProperty() handler will be called instead.  This
305     gives you an opportunity to implement complex "virtual" properties that
306     are not predefined on the object.  If unknownProperty() returns
307     undefined, then set() will simply set the value on the object.
308 
309     Property Observers
310     ---
311 
312     In addition to changing the property, set() will also register a
313     property change with the object.  Unless you have placed this call
314     inside of a beginPropertyChanges() and endPropertyChanges(), any "local"
315     observers (i.e. observer methods declared on the same object), will be
316     called immediately.  Any "remote" observers (i.e. observer methods
317     declared on another object) will be placed in a queue and called at a
318     later time in a coalesced manner.
319 
320     Chaining
321     ---
322 
323     In addition to property changes, set() returns the value of the object
324     itself so you can do chaining like this:
325 
326           record.set('firstName', 'Charles').set('lastName', 'Jolley');
327 
328     @param {String|Hash} key the property to set
329     @param {Object} value the value to set or null.
330     @returns {SC.Observable}
331   */
332   set: function (key, value) {
333     var func   = this[key],
334         notify = this.automaticallyNotifiesObserversFor(key),
335         ret    = value,
336         cachedep, cache, idx, dfunc;
337 
338     if (value === undefined && SC.typeOf(key) === SC.T_HASH) {
339       var hash = key;
340 
341       for (var hashKey in hash) {
342         if (!hash.hasOwnProperty(hashKey)) continue;
343         this.set(hashKey, hash[hashKey]);
344       }
345 
346       return this;
347     }
348 
349     // if there are any dependent keys and they use caching, then clear the
350     // cache.  (If we're notifying, then propertyDidChange will do this for
351     // us.)
352     if (!notify && this._kvo_cacheable && (cache = this._kvo_cache)) {
353       // lookup the cached dependents for this key.  if undefined, compute.
354       // note that if cachdep is set to null is means we figure out it has no
355       // cached dependencies already.  this is different from undefined.
356       cachedep = this._kvo_cachedep;
357       if (!cachedep || (cachedep = cachedep[key]) === undefined) {
358         cachedep = this._kvo_computeCachedDependentsFor(key);
359       }
360 
361       if (cachedep) {
362         idx = cachedep.length;
363         while (--idx >= 0) {
364           dfunc = cachedep[idx];
365           cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
366         }
367       }
368     }
369 
370     // set the value.
371     if (func && func.isProperty) {
372       cache = this._kvo_cache;
373       if (func.isVolatile || !cache || (cache[func.lastSetValueKey] !== value)) {
374         if (!cache) cache = this._kvo_cache = {};
375 
376         cache[func.lastSetValueKey] = value;
377         if (notify) this.propertyWillChange(key);
378         ret = func.call(this, key, value);
379 
380         // update cached value
381         if (func.isCacheable) cache[func.cacheKey] = ret;
382         if (notify) this.propertyDidChange(key, ret, YES);
383       }
384 
385     } else if (func === undefined) {
386       if (notify) this.propertyWillChange(key);
387       this.unknownProperty(key, value);
388       if (notify) this.propertyDidChange(key, ret);
389 
390     } else {
391       if (this[key] !== value) {
392         if (notify) this.propertyWillChange(key);
393         ret = this[key] = value;
394         if (notify) this.propertyDidChange(key, ret);
395       }
396     }
397 
398     return this;
399   },
400 
401   /**
402     Called whenever you try to get or set an undefined property.
403 
404     This is a generic property handler.  If you define it, it will be called
405     when the named property is not yet set in the object.  The default does
406     nothing.
407 
408     @param {String} key the key that was requested
409     @param {Object} value The value if called as a setter, undefined if called as a getter.
410     @returns {Object} The new value for key.
411   */
412   unknownProperty: function (key, value) {
413     if (value !== undefined) { this[key] = value; }
414     return value;
415   },
416 
417   /**
418     Begins a grouping of property changes.
419 
420     You can use this method to group property changes so that notifications
421     will not be sent until the changes are finished.  If you plan to make a
422     large number of changes to an object at one time, you should call this
423     method at the beginning of the changes to suspend change notifications.
424     When you are done making changes, call endPropertyChanges() to allow
425     notification to resume.
426 
427     @returns {SC.Observable}
428   */
429   beginPropertyChanges: function () {
430     this._kvo_changeLevel = this._kvo_changeLevel + 1;
431     return this;
432   },
433 
434   /**
435     Ends a grouping of property changes.
436 
437     You can use this method to group property changes so that notifications
438     will not be sent until the changes are finished.  If you plan to make a
439     large number of changes to an object at one time, you should call
440     beginPropertyChanges() at the beginning of the changes to suspend change
441     notifications. When you are done making changes, call this method to allow
442     notification to resume.
443 
444     @returns {SC.Observable}
445   */
446   endPropertyChanges: function () {
447     this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1;
448     var level = this._kvo_changeLevel, changes = this._kvo_changes;
449     if ((level <= 0) && changes && (changes.length > 0) && !SC.Observers.isObservingSuspended) {
450       this._notifyPropertyObservers();
451     }
452     return this;
453   },
454 
455   /**
456     Notify the observer system that a property is about to change.
457 
458     Sometimes you need to change a value directly or indirectly without
459     actually calling get() or set() on it.  In this case, you can use this
460     method and propertyDidChange() instead.  Calling these two methods
461     together will notify all observers that the property has potentially
462     changed value.
463 
464     Note that you must always call propertyWillChange and propertyDidChange as
465     a pair.  If you do not, it may get the property change groups out of order
466     and cause notifications to be delivered more often than you would like.
467 
468     @param {String} key The property key that is about to change.
469     @returns {SC.Observable}
470   */
471   propertyWillChange: function (key) {
472     return this;
473   },
474 
475   /**
476     Notify the observer system that a property has just changed.
477 
478     Sometimes you need to change a value directly or indirectly without
479     actually calling get() or set() on it.  In this case, you can use this
480     method and propertyWillChange() instead.  Calling these two methods
481     together will notify all observers that the property has potentially
482     changed value.
483 
484     Note that you must always call propertyWillChange and propertyDidChange as
485     a pair. If you do not, it may get the property change groups out of order
486     and cause notifications to be delivered more often than you would like.
487 
488     @param {String} key The property key that has just changed.
489     @param {Object} value The new value of the key.  May be null.
490     @param {Boolean} _keepCache Private property
491     @returns {SC.Observable}
492   */
493   propertyDidChange: function (key, value, _keepCache) {
494     this._kvo_revision = this._kvo_revision + 1;
495     var level = this._kvo_changeLevel,
496         cachedep, idx, dfunc, func;
497 
498     //@if(debug)
499     var log = SC.LOG_OBSERVERS && (this.LOG_OBSERVING !== NO);
500     //@endif
501 
502     // If any dependent keys contain this property in their path,
503     // invalidate the cache of the computed property and re-setup chain with
504     // new value.
505     var chains = this._kvo_property_chains;
506     if (chains) {
507       var keyChains = chains[key];
508 
509       if (keyChains) {
510         this.beginPropertyChanges();
511         keyChains = SC.clone(keyChains);
512         keyChains.forEach(function (chain) {
513           // Invalidate the property that depends on the changed key.
514           chain.notifyPropertyDidChange();
515         });
516         this.endPropertyChanges();
517       }
518     }
519 
520     var cache = this._kvo_cache;
521     if (cache) {
522 
523       // clear any cached value
524       if (!_keepCache) {
525         func = this[key];
526         if (func && func.isProperty) {
527           cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined;
528         }
529       }
530 
531       if (this._kvo_cacheable) {
532         // if there are any dependent keys and they use caching, then clear the
533         // cache.  This is the same code as is in set.  It is inlined for perf.
534         cachedep = this._kvo_cachedep;
535         if (!cachedep || (cachedep = cachedep[key]) === undefined) {
536           cachedep = this._kvo_computeCachedDependentsFor(key);
537         }
538 
539         if (cachedep) {
540           idx = cachedep.length;
541           while (--idx >= 0) {
542             dfunc = cachedep[idx];
543             cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
544           }
545         }
546       }
547     }
548 
549     // save in the change set if queuing changes
550     var suspended = SC.Observers.isObservingSuspended;
551     if ((level > 0) || suspended) {
552       var changes = this._kvo_changes;
553       if (!changes) changes = this._kvo_changes = SC.CoreSet.create();
554       changes.add(key);
555 
556       if (suspended) {
557         //@if(debug)
558         if (log) console.log("%@%@: will not notify observers because observing is suspended".fmt(SC.KVO_SPACES, this));
559         //@endif
560         SC.Observers.objectHasPendingChanges(this);
561       }
562 
563     // otherwise notify property observers immediately
564     } else {
565       this._notifyPropertyObservers(key);
566     }
567 
568     return this;
569   },
570 
571   // ..........................................
572   // DEPENDENT KEYS
573   //
574 
575   /**
576     Use this to indicate that one key changes if other keys it depends on
577     change.  Pass the key that is dependent and additional keys it depends
578     upon.  You can either pass the additional keys inline as arguments or
579     in a single array.
580 
581     You generally do not call this method, but instead pass dependent keys to
582     your property() method when you declare a computed property.
583 
584     You can call this method during your init to register the keys that should
585     trigger a change notification for your computed properties.
586 
587     @param {String} key the dependent key
588     @param {Array|String} dependentKeys one or more dependent keys
589     @returns {Object} this
590   */
591   registerDependentKey: function (key, dependentKeys) {
592     var dependents      = this._kvo_dependents,
593       // chainDependents = this._kvo_chain_dependents,
594       keys, idx, lim, dep, queue;
595 
596     // normalize input.
597     if (typeof dependentKeys === "object" && (dependentKeys instanceof Array)) {
598       keys = dependentKeys;
599       lim  = 0;
600     } else {
601       keys = arguments;
602       lim  = 1;
603     }
604     idx  = keys.length;
605 
606     // define dependents if not defined already.
607     if (!dependents) this._kvo_dependents = dependents = {};
608 
609     // for each key, build array of dependents, add this key...
610     // note that we ignore the first argument since it is the key...
611     while (--idx >= lim) {
612       dep = keys[idx];
613 
614       if (dep.indexOf('.') >= 0) {
615         SC._PropertyChain.createChain(dep, this, key).activate();
616       } else {
617         // add dependent key to dependents array of key it depends on
618         queue = dependents[dep];
619         if (!queue) { queue = dependents[dep] = []; }
620         queue.push(key);
621       }
622     }
623   },
624 
625   /** @private
626     Register a property chain so that dependent keys can be invalidated
627     when a property on this object changes.
628 
629     @param {String} property the property on this object that invalidates the chain
630     @param {SC._PropertyChain} chain the chain to notify
631   */
632   registerDependentKeyWithChain: function (property, chain) {
633     var chains = this._chainsFor(property);
634     chains.add(chain);
635   },
636 
637   /** @private
638     Removes a property chain from the object.
639 
640     @param {String} property the property on this object that invalidates the chain
641     @param {SC._PropertyChain} chain the chain to notify
642   */
643   removeDependentKeyWithChain: function (property, chain) {
644     var chains = this._chainsFor(property);
645     chains.remove(chain);
646 
647     if (chains.get('length') === 0) {
648       delete this._kvo_property_chains[property];
649     }
650   },
651 
652   /** @private
653     Returns an instance of SC.CoreSet in which to save SC._PropertyChains.
654 
655     @param {String} property the property associated with the SC._PropertyChain
656     @returns {SC.CoreSet}
657   */
658   _chainsFor: function (property) {
659     this._kvo_property_chains = this._kvo_property_chains || {};
660     var chains = this._kvo_property_chains[property] || SC.CoreSet.create();
661     this._kvo_property_chains[property] = chains;
662 
663     return chains;
664   },
665 
666   /** @private
667 
668     Helper method used by computeCachedDependents.  Just loops over the
669     array of dependent keys.  If the passed function is cacheable, it will
670     be added to the queue.  Also, recursively call on each keys dependent
671     keys.
672 
673     @param {Array} queue the queue to add functions to
674     @param {Array} keys the array of dependent keys for this key
675     @param {Hash} dependents the _kvo_dependents cache
676     @param {SC.Set} seen already seen keys
677     @returns {void}
678   */
679   _kvo_addCachedDependents: function (queue, keys, dependents, seen) {
680     var idx = keys.length,
681         func, key, deps;
682 
683     while (--idx >= 0) {
684       key  = keys[idx];
685       seen.add(key);
686 
687       // if the value for this key is a computed property, then add it to the
688       // set if it is cacheable, and process any of its dependent keys also.
689       func = this[key];
690       if (func && (func instanceof Function) && func.isProperty) {
691         if (func.isCacheable) queue.push(func); // handle this func
692         if ((deps = dependents[key]) && deps.length > 0) { // and any dependents
693           this._kvo_addCachedDependents(queue, deps, dependents, seen);
694         }
695       }
696     }
697 
698   },
699 
700   /** @private
701 
702     Called by set() whenever it needs to determine which cached dependent
703     keys to clear.  Recursively searches dependent keys to determine all
704     cached property directly or indirectly affected.
705 
706     The return value is also saved for future reference
707 
708     @param {String} key the key to compute
709     @returns {Array}
710   */
711   _kvo_computeCachedDependentsFor: function (key) {
712     var cached     = this._kvo_cachedep,
713         dependents = this._kvo_dependents,
714         keys       = dependents ? dependents[key] : null,
715         queue, seen;
716     if (!cached) cached = this._kvo_cachedep = {};
717 
718     // if there are no dependent keys, then just set and return null to avoid
719     // this mess again.
720     if (!keys || keys.length === 0) return cached[key] = null;
721 
722     // there are dependent keys, so we need to do the work to find out if
723     // any of them or their dependent keys are cached.
724     queue = cached[key] = [];
725     seen  = SC._TMP_SEEN_SET = (SC._TMP_SEEN_SET || SC.CoreSet.create());
726     seen.add(key);
727     this._kvo_addCachedDependents(queue, keys, dependents, seen);
728     seen.clear(); // reset
729 
730     if (queue.length === 0) queue = cached[key] = null; // turns out nothing
731     return queue;
732   },
733 
734   // ..........................................
735   // OBSERVERS
736   //
737 
738   _kvo_for: function (kvoKey, type) {
739     var ret = this[kvoKey];
740 
741     if (!this._kvo_cloned) this._kvo_cloned = {};
742 
743     // if the item does not exist, create it.  Unless type is passed,
744     // assume array.
745     if (!ret) {
746       ret = this[kvoKey] = (type === undefined) ? [] : type.create();
747       this._kvo_cloned[kvoKey] = YES;
748 
749     // if item does exist but has not been cloned, then clone it.  Note
750     // that all types must implement copy().0
751     } else if (!this._kvo_cloned[kvoKey]) {
752       ret = this[kvoKey] = ret.copy();
753       this._kvo_cloned[kvoKey] = YES;
754     }
755 
756     return ret;
757   },
758 
759   /**
760     Adds an observer on a property.
761 
762     This is the core method used to register an observer for a property.
763 
764     Once you call this method, anytime the key's value is set, your observer
765     will be notified.  Note that the observers are triggered anytime the
766     value is set, regardless of whether it has actually changed.  Your
767     observer should be prepared to handle that.
768 
769     You can also pass an optional context parameter to this method.  The
770     context will be passed to your observer method whenever it is triggered.
771     Note that if you add the same target/method pair on a key multiple times
772     with different context parameters, your observer will only be called once
773     with the last context you passed.
774 
775     Observer Methods
776     ---
777 
778     Observer methods you pass should generally have the following signature if
779     you do not pass a "context" parameter:
780 
781           fooDidChange: function (sender, key, value, rev);
782 
783     The sender is the object that changed.  The key is the property that
784     changes.  The value property is currently reserved and unused.  The rev
785     is the last property revision of the object when it changed, which you can
786     use to detect if the key value has really changed or not.
787 
788     If you pass a "context" parameter, the context will be passed before the
789     revision like so:
790 
791           fooDidChange: function (sender, key, value, context, rev);
792 
793     Usually you will not need the value, context or revision parameters at
794     the end.  In this case, it is common to write observer methods that take
795     only a sender and key value as parameters or, if you aren't interested in
796     any of these values, to write an observer that has no parameters at all.
797 
798     @param {String} key the key to observer
799     @param {Object} target the target object to invoke
800     @param {String|Function} method the method to invoke.
801     @param {Object} context optional context
802     @returns {SC.Object} self
803   */
804   addObserver: function (key, target, method, context) {
805     var kvoKey, chain;
806 
807     // normalize.  if a function is passed to target, make it the method.
808     if (method === undefined) {
809       method = target;
810       target = this;
811     }
812     if (!target) target = this;
813 
814     if (typeof method === "string") method = target[method];
815     if (!method) throw new Error("You must pass a method to addObserver()");
816 
817     // Normalize key...
818     key = key.toString();
819     if (key.indexOf('.') >= 0) {
820 
821       // create the chain and save it for later so we can tear it down if
822       // needed.
823       chain = SC._ChainObserver.createChain(this, key, target, method, context);
824       chain.masterTarget = target;
825       chain.masterMethod = method;
826 
827       // Save in set for chain observers.
828       this._kvo_for(SC.keyFor('_kvo_chains', key)).push(chain);
829 
830     // Create observers if needed...
831     } else {
832 
833       // Special case to support reduced properties.  If the property
834       // key begins with '@' and its value is unknown, then try to get its
835       // value.  This will configure the dependent keys if needed.
836       if ((this[key] === undefined) && (key.indexOf('@') === 0)) {
837         this.get(key);
838       }
839 
840       if (target === this) target = null; // use null for observers only.
841       kvoKey = SC.keyFor('_kvo_observers', key);
842       this._kvo_for(kvoKey, SC.ObserverSet).add(target, method, context);
843       this._kvo_for('_kvo_observed_keys', SC.CoreSet).add(key);
844     }
845 
846     if (this.didAddObserver) this.didAddObserver(key, target, method);
847     return this;
848   },
849 
850   /**
851     Remove an observer you have previously registered on this object.  Pass
852     the same key, target, and method you passed to addObserver() and your
853     target will no longer receive notifications.
854 
855     @param {String} key the key to observer
856     @param {Object} target the target object to invoke
857     @param {String|Function} method the method to invoke.
858     @returns {SC.Observable} receiver
859   */
860   removeObserver: function (key, target, method) {
861 
862     var kvoKey, chains, chain, observers, idx;
863 
864     // normalize.  if a function is passed to target, make it the method.
865     if (method === undefined) {
866       method = target;
867       target = this;
868     }
869     if (!target) target = this;
870 
871     if (typeof method === "string") method = target[method];
872     if (!method) throw new Error("You must pass a method to removeObserver()");
873 
874     // if the key contains a '.', this is a chained observer.
875     key = key.toString();
876     if (key.indexOf('.') >= 0) {
877 
878       // try to find matching chains
879       kvoKey = SC.keyFor('_kvo_chains', key);
880       if (chains = this[kvoKey]) {
881 
882         // if chains have not been cloned yet, do so now.
883         chains = this._kvo_for(kvoKey);
884 
885         // remove any chains
886         idx = chains.length;
887         while (--idx >= 0) {
888           chain = chains[idx];
889           if (chain && (chain.masterTarget === target) && (chain.masterMethod === method)) {
890             chains[idx] = chain.destroyChain();
891           }
892         }
893       }
894 
895     // otherwise, just like a normal observer.
896     } else {
897       if (target === this) target = null; // use null for observers only.
898       kvoKey = SC.keyFor('_kvo_observers', key);
899       if (observers = this[kvoKey]) {
900         // if observers have not been cloned yet, do so now
901         observers = this._kvo_for(kvoKey);
902         observers.remove(target, method);
903 
904         // Remove the key when no members remain.
905         if (observers.getMembers().length === 0) {
906           this._kvo_for('_kvo_observed_keys', SC.CoreSet).remove(key);
907         }
908       }
909     }
910 
911     if (this.didRemoveObserver) this.didRemoveObserver(key, target, method);
912     return this;
913   },
914 
915   /**
916     Returns YES if the object currently has observers registered for a
917     particular key.  You can use this method to potentially defer performing
918     an expensive action until someone begins observing a particular property
919     on the object.
920 
921     Optionally, you may pass a target and method to check for the
922     presence of a particular observer. You can use this to avoid creating
923     duplicate observers in situations where that's likely.
924 
925     @param {String} key key to check
926     @param {Object} [target] the target that the observer uses
927     @param {Function|String} [method]) the method on the target that the observer uses
928     @returns {Boolean}
929   */
930   hasObserverFor: function (key, target, method) {
931     SC.Observers.flush(this); // hookup as many observers as possible.
932 
933     var observers = this[SC.keyFor('_kvo_observers', key)],
934         chains = this._kvo_for(SC.keyFor('_kvo_chains', key)),
935         locals = this[SC.keyFor('_kvo_local', key)],
936         isChain = key.indexOf('.') >= 0;
937 
938     // Fast path: no target/method.
939     if (target === undefined) {
940       if (isChain) {
941         if (chains && chains.length > 0) return YES;
942       } else {
943         // Found locally.
944         if (locals && locals.length > 0) return YES;
945         if (observers && observers.getMembers().length > 0) return YES;
946       }
947       return NO;
948 
949     // Slow path: target/method.
950     } else {
951       if (method === undefined) {
952         method = target;
953         target = this;
954       }
955       if (typeof method === "string") method = target[method];
956       if (!method) throw new Error("Developer Error: If present, the `method` argument of hasObserverFor must be (or refer to) a function.");
957 
958       // Declare our iterators.
959       var i, len;
960 
961       // Check remote chains.
962       if (isChain) {
963         if (!chains || !chains.length) return NO;
964         len = chains.length;
965         for (i = 0; i < len; i++) {
966           if (chains[i].masterTarget === target && chains[i].masterMethod === method) return YES;
967         }
968         return NO;
969 
970       // Check locals.
971       } else if (target === this) {
972         if (!locals) return NO;
973         len = locals.length;
974         for (i = 0; i < len; i++) {
975           if (this[locals[i]] === method) return YES;
976         }
977         return NO;
978 
979       // Check remotes.
980       } else {
981         if (!observers || !observers.members) return NO;
982 
983         len = observers.members.length;
984         var member;
985         for (i = 0; i < len; i++) {
986           member = observers.members[i];
987           // If this is a non-chained observer, the first item is the target and the second is the method.
988           if (member[0] === target && member[1] === method) return YES;
989         }
990         return NO;
991       }
992       // TODO: Remote chains.
993     }
994   },
995 
996   /**
997     This method will register any observers and computed properties saved on
998     the object.  Normally you do not need to call this method yourself.  It
999     is invoked automatically just before property notifications are sent and
1000     from the init() method of SC.Object.  You may choose to call this
1001     from your own initialization method if you are using SC.Observable in
1002     a non-SC.Object-based object.
1003 
1004     This method looks for several private variables, which you can setup,
1005     to initialize:
1006 
1007       - _observers: this should contain an array of key names for observers
1008         you need to configure.
1009 
1010       - _bindings: this should contain an array of key names that configure
1011         bindings.
1012 
1013       - _properties: this should contain an array of key names for computed
1014         properties.
1015 
1016     @returns {Object} this
1017   */
1018   initObservable: function () {
1019     if (this._observableInited) return;
1020     this._observableInited = YES;
1021 
1022     var loc, keys, key, value, observer, propertyPaths, propertyPathsLength,
1023         len, ploc, path, propertyKey, keysLen;
1024 
1025     // Loop through observer functions and register them
1026     if (keys = this._observers) {
1027       len = keys.length;
1028       for (loc = 0; loc < len; loc++) {
1029         key = keys[loc];
1030         observer = this[key];
1031         propertyPaths = observer.propertyPaths;
1032         propertyPathsLength = (propertyPaths) ? propertyPaths.length : 0;
1033         for (ploc = 0 ; ploc < propertyPathsLength; ploc++) {
1034           path = propertyPaths[ploc];
1035           this.addObservesHandler(observer, path);
1036         }
1037       }
1038     }
1039 
1040     // Add Bindings
1041     this.bindings = []; // will be filled in by the bind() method.
1042     if (keys = this._bindings) {
1043       for (loc = 0, keysLen = keys.length; loc < keysLen; loc++) {
1044         // get propertyKey
1045         key = keys[loc];
1046         value = this[key];
1047         propertyKey = key.slice(0, -7); // contentBinding => content
1048 
1049         // Replace the short form property with the new binding object.
1050         this[key] = this.bind(propertyKey, value);
1051       }
1052     }
1053 
1054     // Add Properties
1055     if (keys = this._properties) {
1056       for (loc = 0, keysLen = keys.length; loc < keysLen; loc++) {
1057         key = keys[loc];
1058         if (value = this[key]) {
1059 
1060           // activate cacheable only if needed for perf reasons
1061           if (value.isCacheable) this._kvo_cacheable = YES;
1062 
1063           // register dependent keys
1064           if (value.dependentKeys && (value.dependentKeys.length > 0)) {
1065             this.registerDependentKey(key, value.dependentKeys);
1066           }
1067         }
1068       }
1069     }
1070 
1071     // Clean up these properties once they have been used.
1072     delete this._bindings;
1073     delete this._properties;
1074 
1075     return this;
1076   },
1077 
1078   /**
1079     This method will destroy the observable.
1080 
1081     @returns {Object} this
1082   */
1083   destroyObservable: function () {
1084     var key, keys,
1085       len,
1086       observer,
1087       path,
1088       propertyPaths,
1089       propertyPathsLength;
1090 
1091     // Destroy bindings
1092     this.bindings.invoke('destroy');
1093     delete this.bindings;
1094 
1095     // Loop through observer functions and remove them
1096     if (keys = this._observers) {
1097       len = keys.length;
1098       for (var loc = 0; loc < len; loc++) {
1099         key = keys[loc];
1100         observer = this[key];
1101         propertyPaths = observer.propertyPaths;
1102         propertyPathsLength = (propertyPaths) ? propertyPaths.length : 0;
1103 
1104         for (var ploc = 0; ploc < propertyPathsLength; ploc++) {
1105           path = propertyPaths[ploc];
1106           this.removeObservesHandler(observer, path);
1107         }
1108       }
1109     }
1110 
1111     delete this._observers;
1112 
1113     return this;
1114   },
1115 
1116   /**
1117     Will add an observes handler to this object for a given property path.
1118 
1119     In most cases, the path provided is relative to this object. However,
1120     if the path begins with a capital character then the path is considered
1121     relative to the window object.
1122 
1123     @param {Function} observer the function on this object that will be
1124       notified of changes
1125     @param {String} path a property path string
1126     @return {Object} returns this
1127   */
1128   addObservesHandler: function (observer, path) {
1129     this._configureObservesHandler(SC.OBSERVES_HANDLER_ADD, observer, path);
1130     return this;
1131   },
1132 
1133   /**
1134     Will remove an observes handler from this object for a given property path.
1135 
1136     In most cases, the path provided is relative to this object. However,
1137     if the path begins with a capital character then the path is considered
1138     relative to the window object.
1139 
1140     @param {Function} observer the function on this object that will be
1141       notified of changes
1142     @param {String} path a property path string
1143     @return {Object} returns this
1144   */
1145   removeObservesHandler: function (observer, path) {
1146     this._configureObservesHandler(SC.OBSERVES_HANDLER_REMOVE, observer, path);
1147     return this;
1148   },
1149 
1150   /** @private
1151 
1152     Used to either add or remove an observer handler on this object
1153     for a given property path.
1154 
1155     In most cases, the path provided is relative to this object. However,
1156     if the path begins with a capital character then the path is considered
1157     relative to the window object.
1158 
1159     You must supply an action that is to be performed by this method. The
1160     action can either be `SC.OBSERVES_HANDLER_ADD` or `SC.OBSERVES_HANDLER_REMOVE`.
1161 
1162     @param {Function} observer the function on this object that will be
1163       notified of changes
1164     @param {String} path a property path string
1165     @param {String} path a dot-notation property path string
1166   */
1167   _configureObservesHandler: function (action, observer, path) {
1168     var dotIndex, root;
1169 
1170     switch (action) {
1171     case SC.OBSERVES_HANDLER_ADD:
1172       action = "addObserver";
1173       break;
1174     case SC.OBSERVES_HANDLER_REMOVE:
1175       action = "removeObserver";
1176       break;
1177     default:
1178       throw new Error("invalid action provided: " + action);
1179     }
1180 
1181     dotIndex = path.indexOf('.');
1182 
1183     if (dotIndex < 0) {
1184       this[action](path, this, observer);
1185     } else if (path.indexOf('*') === 0) {
1186       this[action](path.slice(1), this, observer);
1187     } else {
1188       root = null;
1189 
1190       if (dotIndex === 0) {
1191         root = this;
1192         path = path.slice(1);
1193       } else if (dotIndex === 4 && path.slice(0, 5) === 'this.') {
1194         root = this;
1195         path = path.slice(5);
1196       } else if (dotIndex < 0 && path.length === 4 && path === 'this') {
1197         root = this;
1198         path = '';
1199       } else if (dotIndex > 0 && path[0] === path.charAt(0).toLowerCase()) {
1200         // if the first character for the given path is lower case
1201         // then we assume the path is relative to this
1202         root = this;
1203       }
1204 
1205       SC.Observers[action](path, this, observer, root);
1206     }
1207   },
1208 
1209   // ..........................................
1210   // NOTIFICATION
1211   //
1212 
1213   /**
1214     Returns an array with all of the observers registered for the specified
1215     key.  This is intended for debugging purposes only.  You generally do not
1216     want to rely on this method for production code.
1217 
1218     @param {String} key the key to evaluate
1219     @returns {Array} array of Observer objects, describing the observer.
1220   */
1221   observersForKey: function (key) {
1222     SC.Observers.flush(this); // hookup as many observers as possible.
1223 
1224     var observers = this[SC.keyFor('_kvo_observers', key)];
1225     return observers ? observers.getMembers() : [];
1226   },
1227 
1228   /** @private
1229     This private method actually notifies the observers for any keys in the observer queue.  If you
1230     pass a key it will be added to the queue.
1231   */
1232   _notifyPropertyObservers: function (key) {
1233     // Ensure that this object has been initialized.
1234     if (!this._observableInited) this.initObservable();
1235 
1236     SC.Observers.flush(this); // hookup as many observers as possible.
1237 
1238     var observers, changes, dependents, starObservers, idx, keys, rev,
1239         members, membersLength, member, memberLoc, target, method, loc, func,
1240         context, spaces, cache;
1241 
1242     //@if(debug)
1243     var log = SC.LOG_OBSERVERS && this.LOG_OBSERVING !== NO;
1244     if (log) {
1245       spaces = SC.KVO_SPACES = (SC.KVO_SPACES || '') + '  ';
1246       console.log('%@%@: notifying observers after change to key "%@"'.fmt(spaces, this, key));
1247     }
1248     //@endif
1249 
1250     // Get any starObservers -- they will be notified of all changes.
1251     starObservers = this['_kvo_observers_*'];
1252 
1253     // prevent notifications from being sent until complete
1254     this._kvo_changeLevel = this._kvo_changeLevel + 1;
1255 
1256     // keep sending notifications as long as there are changes
1257     while (((changes = this._kvo_changes) && (changes.length > 0)) || key) {
1258 
1259       // increment revision
1260       rev = ++this.propertyRevision;
1261 
1262       // save the current set of changes and swap out the kvo_changes so that
1263       // any set() calls by observers will be saved in a new set.
1264       if (!changes) changes = SC.CoreSet.create();
1265       this._kvo_changes = null;
1266 
1267       // Add the passed key to the changes set.  If a '*' was passed, then
1268       // add all keys in the observers to the set...
1269       // once finished, clear the key so the loop will end.
1270       if (key === '*') {
1271         changes.add('*');
1272         changes.addEach(this._kvo_for('_kvo_observed_keys', SC.CoreSet));
1273 
1274       } else if (key) {
1275         changes.add(key);
1276       }
1277 
1278       // Now go through the set and add all dependent keys...
1279       dependents = this._kvo_dependents;
1280       if (dependents) {
1281 
1282         // NOTE: each time we loop, we check the changes length, this
1283         // way any dependent keys added to the set will also be evaluated...
1284         for (idx = 0; idx < changes.length; idx++) {
1285           key = changes[idx];
1286           keys = dependents[key];
1287 
1288           // for each dependent key, add to set of changes.  Also, if key
1289           // value is a cacheable property, clear the cached value...
1290           if (keys && (loc = keys.length)) {
1291             //@if(debug)
1292             if (log) {
1293               console.log("%@...including dependent keys for %@: %@".fmt(spaces, key, keys));
1294             }
1295             //@endif
1296             cache = this._kvo_cache;
1297             if (!cache) cache = this._kvo_cache = {};
1298             while (--loc >= 0) {
1299               changes.add(key = keys[loc]);
1300               if (func = this[key]) {
1301                 this[func.cacheKey] = undefined;
1302                 cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined;
1303               } // if (func=)
1304             } // while (--loc)
1305           } // if (keys &&
1306         } // for(idx...
1307       } // if (dependents...)
1308 
1309       // now iterate through all changed keys and notify observers.
1310       while (changes.length > 0) {
1311         key = changes.pop(); // the changed key
1312 
1313         // find any observers and notify them...
1314         observers = this[SC.keyFor('_kvo_observers', key)];
1315 
1316         if (observers) {
1317           // We need to clone the 'members' structure here in case any of the
1318           // observers we're about to notify happen to remove observers for
1319           // this key, which would mutate the structure underneath us.
1320           // (Cloning it rather than mutating gives us a clear policy:  if you
1321           // were registered as an observer at the time notification begins,
1322           // you will be notified, regardless of whether you're removed as an
1323           // observer during that round of notification.  Similarly, if you're
1324           // added as an observer during the notification round by another
1325           // observer, you will not be notified until the next time.)
1326           members = observers.getMembers();
1327           membersLength = members.length;
1328 
1329           for (memberLoc = 0; memberLoc < membersLength; memberLoc++) {
1330             member = members[memberLoc];
1331 
1332             if (member[3] === rev) continue; // skip notified items.
1333 
1334             if (!member[1]) console.log(member);
1335 
1336             target = member[0] || this;
1337             method = member[1];
1338             context = member[2];
1339             member[3] = rev;
1340 
1341             //@if(debug)
1342             if (log) console.log('%@...firing observer on %@ for key "%@"'.fmt(spaces, target, key));
1343             //@endif
1344             if (context !== undefined) {
1345               method.call(target, this, key, null, context, rev);
1346             } else {
1347               method.call(target, this, key, null, rev);
1348             }
1349           }
1350         }
1351 
1352         // look for local observers.  Local observers are added by SC.Object
1353         // as an optimization to avoid having to add observers for every
1354         // instance when you are just observing your local object.
1355         members = this[SC.keyFor('_kvo_local', key)];
1356         if (members) {
1357           // Note:  Since, unlike above, we don't expect local observers to be
1358           //        removed in general, we will not clone 'members'.
1359           membersLength = members.length;
1360           for (memberLoc = 0; memberLoc < membersLength; memberLoc++) {
1361             member = members[memberLoc];
1362             method = this[member]; // try to find observer function
1363             if (method) {
1364               //@if(debug)
1365               if (log) console.log('%@...firing local observer %@.%@ for key "%@"'.fmt(spaces, this, member, key));
1366               //@endif
1367 
1368               method.call(this, this, key, null, rev);
1369             }
1370           }
1371         }
1372 
1373         // if there are starObservers, do the same thing for them
1374         if (starObservers && key !== '*') {
1375           // We clone the structure per the justification, above, for regular
1376           // observers.
1377           members = starObservers.getMembers();
1378           membersLength = members.length;
1379           for (memberLoc = 0; memberLoc < membersLength; memberLoc++) {
1380             member = members[memberLoc];
1381             target = member[0] || this;
1382             method = member[1];
1383             context = member[2];
1384 
1385             //@if(debug)
1386             if (log) console.log('%@...firing * observer on %@ for key "%@"'.fmt(spaces, target, key));
1387             //@endif
1388             if (context !== undefined) {
1389               method.call(target, this, key, null, context, rev);
1390             } else {
1391               method.call(target, this, key, null, rev);
1392             }
1393           }
1394         }
1395 
1396         // if there is a default property observer, call that also
1397         if (this.propertyObserver) {
1398           //@if(debug)
1399           if (log) console.log('%@...firing %@.propertyObserver for key "%@"'.fmt(spaces, this, key));
1400           //@endif
1401           this.propertyObserver(this, key, null, rev);
1402         }
1403       } // while(changes.length>0)
1404 
1405       // changes set should be empty. release it for reuse
1406       if (changes) changes.destroy();
1407 
1408       // key is no longer needed; clear it to avoid infinite loops
1409       key = null;
1410 
1411     } // while (changes)
1412 
1413     // done with loop, reduce change level so that future sets can resume
1414     this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1;
1415 
1416     //@if(debug)
1417     if (log) SC.KVO_SPACES = spaces.slice(0, -2);
1418     //@endif
1419 
1420     return YES; // finished successfully
1421   },
1422 
1423   // ..........................................
1424   // BINDINGS
1425   //
1426 
1427   /**
1428     Manually add a new binding to an object.  This is the same as doing
1429     the more familiar propertyBinding: 'property.path' approach.
1430 
1431     @param {String} toKey the key to bind to
1432     @param {Object} target target or property path to bind from
1433     @param {String|Function} method method for target to bind from
1434     @returns {SC.Binding} new binding instance
1435   */
1436   bind: function (toKey, target, method) {
1437     var binding, pathType;
1438 
1439     //@if(debug)
1440     // Developer support.
1441     if (!target) {
1442       throw new Error("Developer Error: Attempt to bind key `%@` to null or undefined target".fmt(toKey));
1443     }
1444     //@endif
1445 
1446     // normalize...
1447     if (method !== undefined) target = [target, method];
1448 
1449     pathType = typeof target;
1450 
1451     // if a string or array (i.e. tuple) is passed, convert this into a
1452     // binding.  If a binding default was provided, use that.
1453     if (pathType === "string" || (pathType === "object" && (target instanceof Array))) {
1454       binding = this[toKey + 'BindingDefault'] || SC.Binding;
1455       binding = binding.beget().from(target);
1456     } else {
1457       // If a binding object was provided, clone it so that it gets
1458       // connected again if the original example binding was already
1459       // connected.
1460       binding = target.beget();
1461     }
1462 
1463     // finish configuring the binding and then connect it.
1464     binding = binding.to(toKey, this).connect();
1465     this.bindings.push(binding);
1466 
1467     return binding;
1468   },
1469 
1470   /**
1471     didChangeFor is a very important method which allows you to tell whether
1472     a property or properties have changed.
1473 
1474     The key to using didChangeFor is to pass a unique string as the first argument,
1475     which signals, "Has anything changed since the last time this was called with
1476     this unique key?" The string can be anything you want, as long as it's unique
1477     and stays the same from call to call.
1478 
1479     After the key argument, you can pass as many property arguments as you like;
1480     didChangeFor will only return `true` if any of those properties have changed
1481     since the last call.
1482 
1483     For example, in your view's update method, you might want to gate DOM changes
1484     (generally a slow operation) on whether the root values have changed. You might
1485     ask the following:
1486 
1487         if (this.didChangeFor('updateOnDisplayValue', 'displayValue')) {
1488           // Update the DOM.
1489         }
1490 
1491     In another method on the same view, you might send an event if that same value
1492     has changed:
1493 
1494         if (this.didChangeFor('otherMethodDisplayValue', 'displayValue')) {
1495           // Send a statechart action.
1496         }
1497 
1498     Each call will correctly return whether the property has changed since the last
1499     time displayDidChange was called *with that key*. The following sequence of calls
1500     will return the following values:
1501 
1502       - this.set('displayValue', 'value1');
1503       - this.didChangeFor('updateOnDisplayValue', 'displayValue');
1504       > true;
1505       - this.didChangeFor('updateOnDisplayValue', 'displayValue');
1506       > false;
1507       - this.didChangeFor('otherMethodDisplayValue', 'displayValue');
1508       > true;
1509       - this.set('displayValue', 'value2');
1510       - this.didChangeFor('updateOnDisplayValue', 'displayValue');
1511       > true;
1512       - this.didChangeFor('updateOnDisplayValue', 'displayValue');
1513       > false;
1514       - this.didChangeFor('updateOnDisplayValue', 'displayValue');
1515       > false;
1516       - this.didChangeFor('otherMethodDisplayValue', 'displayValue');
1517       > false;
1518 
1519     This method works by comparing property revision counts. Every time a
1520     property changes, an internal counter is incremented. When didChangeFor is
1521     invoked, the current revision count of the property is compared to the
1522     revision count from the last time this method was called.
1523 
1524     @param {String|Object} context a unique identifier
1525     @param {String…} propertyNames one or more property names
1526   */
1527   didChangeFor: function (context) {
1528     var valueCache, revisionCache, seenValues, seenRevisions, ret,
1529         currentRevision, idx, key, value;
1530     context = SC.hashFor(context); // get a hash key we can use in caches.
1531 
1532     // setup caches...
1533     valueCache = this._kvo_didChange_valueCache;
1534     if (!valueCache) valueCache = this._kvo_didChange_valueCache = {};
1535     revisionCache = this._kvo_didChange_revisionCache;
1536     if (!revisionCache) revisionCache = this._kvo_didChange_revisionCache = {};
1537 
1538     // get the cache of values and revisions already seen in this context
1539     seenValues = valueCache[context] || {};
1540     seenRevisions = revisionCache[context] || {};
1541 
1542     // prepare to loop!
1543     ret = false;
1544     currentRevision = this._kvo_revision;
1545     idx = arguments.length;
1546     while (--idx >= 1) {  // NB: loop only to 1 to ignore context arg.
1547       key = arguments[idx];
1548 
1549       // has the kvo revision changed since the last time we did this?
1550       if (seenRevisions[key] != currentRevision) {
1551         // yes, check the value with the last seen value
1552         value = this.get(key);
1553         if (seenValues[key] !== value) {
1554           ret = true; // did change!
1555           seenValues[key] = value;
1556         }
1557       }
1558       seenRevisions[key] = currentRevision;
1559     }
1560 
1561     valueCache[context] = seenValues;
1562     revisionCache[context] = seenRevisions;
1563     return ret;
1564   },
1565 
1566   /**
1567     Sets the property only if the passed value is different from the
1568     current value.  Depending on how expensive a get() is on this property,
1569     this may be more efficient.
1570 
1571     NOTE: By default, the set() method will not set the value unless it has
1572     changed. However, this check can skipped by setting .property().idempotent(NO)
1573     setIfChanged() may be useful in this case.
1574 
1575     @param {String|Hash} key the key to change
1576     @param {Object} value the value to change
1577     @returns {SC.Observable}
1578   */
1579   setIfChanged: function (key, value) {
1580     if (value === undefined && SC.typeOf(key) === SC.T_HASH) {
1581       var hash = key;
1582 
1583       for (key in hash) {
1584         if (!hash.hasOwnProperty(key)) continue;
1585         this.setIfChanged(key, hash[key]);
1586       }
1587 
1588       return this;
1589     }
1590 
1591     return (this.get(key) !== value) ? this.set(key, value) : this;
1592   },
1593 
1594   /**
1595     Navigates the property path, returning the value at that point.
1596 
1597     If any object in the path is undefined, returns undefined.
1598     @param {String} path The property path you want to retrieve
1599   */
1600   getPath: function (path) {
1601     var tuple = SC.tupleForPropertyPath(path, this);
1602     if (tuple === null || tuple[0] === null) return undefined;
1603     return SC.get(tuple[0], tuple[1]);
1604   },
1605 
1606   /**
1607     Navigates the property path, finally setting the value.
1608 
1609     @param {String} path the property path to set
1610     @param {Object} value the value to set
1611     @returns {SC.Observable}
1612   */
1613   setPath: function (path, value) {
1614     if (path.indexOf('.') >= 0) {
1615       var tuple = SC.tupleForPropertyPath(path, this);
1616       if (!tuple || !tuple[0]) return null;
1617       tuple[0].set(tuple[1], value);
1618     } else this.set(path, value); // shortcut
1619     return this;
1620   },
1621 
1622   /**
1623     Navigates the property path, finally setting the value but only if
1624     the value does not match the current value.  This will avoid sending
1625     unnecessary change notifications.
1626 
1627     @param {String} path the property path to set
1628     @param {Object} value the value to set
1629     @returns {Object} this
1630   */
1631   setPathIfChanged: function (path, value) {
1632     if (path.indexOf('.') >= 0) {
1633       var tuple = SC.tupleForPropertyPath(path, this);
1634       if (!tuple || !tuple[0]) return null;
1635       if (tuple[0].get(tuple[1]) !== value) {
1636         tuple[0].set(tuple[1], value);
1637       }
1638     } else this.setIfChanged(path, value); // shortcut
1639     return this;
1640   },
1641 
1642   /**
1643     Convenience method to get an array of properties.
1644 
1645     Pass in multiple property keys or an array of property keys.  This
1646     method uses getPath() so you can also pass key paths.
1647 
1648     @returns {Array} Values of property keys.
1649   */
1650   getEach: function () {
1651     var ret = [], idx, idxLen;
1652 
1653     for (idx = 0, idxLen = arguments.length; idx < idxLen; idx++) {
1654       ret[ret.length] = this.getPath(arguments[idx]);
1655     }
1656     return ret;
1657   },
1658 
1659 
1660   /**
1661     Increments the value of a property.
1662 
1663     @param {String} key property name
1664     @param {Number} increment the amount to increment (optional)
1665     @returns {Number} new value of property
1666   */
1667   incrementProperty: function (key, increment) {
1668     if (!increment) increment = 1;
1669     this.set(key, (this.get(key) || 0) + increment);
1670     return this.get(key);
1671   },
1672 
1673   /**
1674     Decrements the value of a property.
1675 
1676     @param {String} key property name
1677     @param {Number} increment the amount to decrement (optional)
1678     @returns {Number} new value of property
1679   */
1680   decrementProperty: function (key, increment) {
1681     if (!increment) increment = 1;
1682     this.set(key, (this.get(key) || 0) - increment);
1683     return this.get(key);
1684   },
1685 
1686   /**
1687     Inverts a property.  Property should be a bool.
1688 
1689     @param {String} key property name
1690     @param {Object} value optional parameter for "true" value
1691     @param {Object} alt optional parameter for "false" value
1692     @returns {Object} new value
1693   */
1694   toggleProperty: function (key, value, alt) {
1695     if (value === undefined) value = true;
1696     if (alt === undefined) alt = false;
1697     value = (this.get(key) == value) ? alt : value;
1698     this.set(key, value);
1699     return this.get(key);
1700   },
1701 
1702   /**
1703     Convenience method to call propertyWillChange/propertyDidChange.
1704 
1705     Sometimes you need to notify observers that a property has changed value
1706     without actually changing this value.  In those cases, you can use this
1707     method as a convenience instead of calling propertyWillChange() and
1708     propertyDidChange().
1709 
1710     @param {String} key The property key that has just changed.
1711     @param {Object} value The new value of the key.  May be null.
1712     @returns {SC.Observable}
1713   */
1714   notifyPropertyChange: function (key, value) {
1715     this.propertyWillChange(key);
1716     this.propertyDidChange(key, value);
1717     return this;
1718   },
1719 
1720   /**
1721     Notifies observers of all possible property changes.
1722 
1723     Sometimes when you make a major update to your object, it is cheaper to
1724     simply notify all observers that their property might have changed than
1725     to figure out specifically which properties actually did change.
1726 
1727     In those cases, you can simply call this method to notify all property
1728     observers immediately.  Note that this ignores property groups.
1729 
1730     @returns {SC.Observable}
1731   */
1732   allPropertiesDidChange: function () {
1733     this._kvo_cache = null; //clear cached props
1734     this._notifyPropertyObservers('*');
1735     return this;
1736   },
1737 
1738   propertyRevision: 1
1739 
1740 };
1741 
1742 //@if(debug)
1743 /** @private used by addProbe/removeProbe. Debug mode only. */
1744 SC.logChange = function logChange(target, key, value) {
1745   console.log("CHANGE: %@[%@] => %@".fmt(target, key, target.get(key)));
1746 };
1747 //@endif
1748 
1749 /**
1750   Retrieves a property from an object, using get() if the
1751   object implements SC.Observable.
1752 
1753   @param  {Object}  object  the object to query
1754   @param  {String}  key the property to retrieve
1755 */
1756 SC.mixin(SC, {
1757 
1758   get: function (object, key) {
1759     if (!object) return undefined;
1760     if (key === undefined) return this[object];
1761     if (object.get) return object.get(key);
1762     return object[key];
1763   },
1764 
1765   /**
1766     Retrieves a property from an object at a specified path, using get() if
1767     the object implements SC.Observable.
1768 
1769     @param  {Object}  object  the object to query
1770     @param  {String}  path the path to the property to retrieve
1771   */
1772   getPath: function (object, path) {
1773     if (path === undefined) {
1774       path = object;
1775       object = window;
1776     }
1777     return SC.objectForPropertyPath(path, object);
1778   }
1779 
1780 });
1781 
1782 // Make all Array's observable
1783 SC.mixin(Array.prototype, SC.Observable);
1784