1 /*
  2  *  Copyright © 2009 Apple Inc. All rights reserved.
  3  */
  4  
  5 /* ==================== Constants ==================== */
  6 
  7 const TKObjectPropertyChanged = 'handlePropertyChange';
  8 
  9 /* ==================== TKObject ==================== */
 10 
 11 /**
 12  *  @class
 13  *
 14  *  <p>The TKObject class is the base class for all TuneKit objects and provides a way to receive and trigger
 15  *  notifications when a JavaScript property changes.</p>
 16  *
 17  *  <p>Observing a property on an object is done through the
 18  *  {@link #addPropertyObserver} method, specifying the property to observe as well as the object that will
 19  *  receive the notifications of the property change. Likewise, an object can stop observing such changes using
 20  *  the {@link #removePropertyObserver} method. When a property value changes, the author needs to call the 
 21  *  {@link #notifyPropertyChange} method so that registered observers will be notified. Note that synthetized
 22  *  properties will automatically trigger notifications when changed, so in this case, setters need not 
 23  *  manually call {@link #notifyPropertyChange}.</p>
 24  *
 25  *  <p>Finally, TKObject also provides the {@link #callSuper} method to call the superclass's implementation
 26  *  of an overload method.</p>
 27  *  
 28  *  @since TuneKit 1.0
 29  */
 30 function TKObject () {
 31   this.observedProperties = {};
 32 };
 33 
 34 /* ==================== Method overriding ==================== */
 35 
 36 /**
 37  *  This method calls the object's superclass implementation of the current method
 38  */
 39 TKObject.prototype.callSuper = function () {
 40   // figure out what function called us
 41   var caller = TKObject.prototype.callSuper.caller;
 42   // if that function is a constructor, we call the parent class's constructor
 43   if (TKUtils.objectHasMethod(caller, 'inherits')) {
 44     caller.inherits.apply(this, arguments);
 45   }
 46   // otherwise we look at that function name in the parent prototype
 47   else {
 48     var parent = caller._class.inherits.prototype;
 49     var method_name = caller._name;
 50     if (TKUtils.objectHasMethod(parent, method_name)) {
 51       return parent[method_name].apply(this, arguments);
 52     }
 53   }
 54 };
 55 
 56 /* ==================== Observing ==================== */
 57 
 58 /**
 59  *  Indicates whether this object has registered observers for this property
 60  *
 61  *  @param {String} propertyName The property that may be observed.
 62  *
 63  *  @returns {bool} Whether this object has registered observers for this property
 64  *  @private
 65  */
 66 TKObject.prototype.isPropertyObserved = function (propertyName) {
 67   return !TKUtils.objectIsUndefined(this.observedProperties[propertyName]);
 68 };
 69 
 70 /**
 71  *  Adds an observer for the named property on this object. In case an existing
 72  *  combination of the observer and property is already registered, this method
 73  *  is a no-op.
 74  *
 75  *  @param {String} propertyName The property to observe.
 76  *  @param {Object} observer The object that will be notified when the property is changed.
 77  *  @param {String} methodName The optional method name that will be called on the observer when the property is changed. 
 78  *  If this property is not provided, the <code>observer</code> must implement the {@link TKPropertyValueChange} protocol.
 79  */
 80 TKObject.prototype.addPropertyObserver = function (propertyName, observer, methodName) {
 81   // create the array for this property if it's not already there
 82   if (!this.isPropertyObserved(propertyName)) {
 83     this.observedProperties[propertyName] = new Array();
 84   }
 85   // do nothing if we already have this observer registered
 86   else if (this.observedProperties[propertyName].indexOf(observer) > -1) {
 87     return;
 88   }
 89   // now, add the observer to the observer array for this property if valid
 90   var methodName = methodName || TKObjectPropertyChanged;
 91   if (TKUtils.objectHasMethod(observer, methodName)) {
 92     this.observedProperties[propertyName].push({
 93       observer: observer,
 94       methodName : methodName
 95     });
 96   }
 97 };
 98 
 99 /**
100  *  Removes the observer for the named property on this object. In case an existing
101  *  combination of the observer and property is not already registered, this method
102  *  is a no-op.
103  *
104  *  @param {String} propertyName The observed property.
105  *  @param {Object} observer The object that was notified when the property changed.
106  *
107  *  @returns {bool} Whether an observer was removed
108  */
109 TKObject.prototype.removePropertyObserver = function (propertyName, observer) {
110   // do nothing if this property is not observed
111   if (!this.isPropertyObserved(propertyName)) {
112     return false;
113   }
114   // now get the observer's index in the array
115   var observers = this.observedProperties[propertyName];
116   var observer_index = observers.indexOf(observer);
117   // remove the observer if it was registered
118   if (observer_index > -1) {
119     observers.splice(observer_index, 1);
120   }
121   // let the user know whether we succeeded
122   return (observer_index > -1);
123 };
124 
125 /**
126  *  Triggers a notification that the given property on this object has changed.
127  *  For synthesized properties, this is called automatically upon setting the
128  *  property. For methods that update an instance variable that is not synthesized,
129  *  {@link #notifyPropertyChange} has to be called manually so that observers are notified.
130  *
131  *  @param {String} propertyName The observed property.
132  */
133 TKObject.prototype.notifyPropertyChange = function (propertyName) {
134   // do nothing if this property is not observed
135   if (!this.isPropertyObserved(propertyName)) {
136     return;
137   }
138   // now, go through each observer for this property and notify them
139   var observers = this.observedProperties[propertyName];
140   for (var i = 0; i < observers.length; i++) {
141     var observer = observers[i];
142     observer.observer[observer.methodName](this, propertyName);
143   }
144 };
145 
146 /**
147  *  Calls a method on this object after a delay, allowing any number of optional extra arguments to be
148  *  passed after the first two mandatory ones.
149  *
150  *  @param {String} methodName The method name to be called.
151  *  @param {int} delay The delay in miliseconds.
152  *
153  *  @returns {int} The timeout that can be used to call <code>clearTimeout</code>.
154  */
155 TKObject.prototype.callMethodNameAfterDelay = function (methodName, delay) {
156   var _this = this;
157   var args = Array.prototype.slice.call(arguments, 2);
158   var generated_function = function () {
159     _this[methodName].apply(_this, args);
160   };
161   generated_function.displayName = TKUtils.createDisplayName(this.constructor.name, methodName);
162   return setTimeout(generated_function, delay);
163 };
164 
165 /**
166  *  @class
167  *  @name TKPropertyValueChange
168  *  @since TuneKit 1.0
169  */
170 
171 /**
172  *  Invoked when a property on an observed object has been changed.
173  *
174  *  @name handlePropertyChange
175  *  @function
176  *
177  *  @param {Object} observedObject The observed object
178  *  @param {String} propertyName The observed property's name as a string
179  *  @memberOf TKPropertyValueChange.prototype
180  */
181 
182 TKClass(TKObject, 'TKObject');
183