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