1 include(bbq.util.BBQUtil); 2 include(bbq.util.Log); 3 4 bbq.lang.Watchable = new Class.create(/** @lends bbq.lang.Watchable.prototype */ { 5 /** 6 * Storage for callbacks registered on this object 7 * 8 * @type {Object} 9 */ 10 _callbacks: null, 11 12 /** 13 * @constructs 14 */ 15 initialize: function() { 16 this._callbacks = {}; 17 }, 18 19 /** 20 * Returns a callback array, initialising it where necessary 21 * 22 * @param {String} type 23 * @return {Array} 24 */ 25 _getCallbacks: function(type) { 26 if(Object.isUndefined(this._callbacks[type])) { 27 this._callbacks[type] = new Hash(); 28 } 29 30 return this._callbacks[type]; 31 }, 32 33 /** 34 * @example 35 * <pre><code class="language-javascript"> 36 * this._dropDown.registerListener("onchange", this._subTypeChanged.bind(this)); 37 * </code></pre> 38 * @param {String} type 39 * @param {Function} callback 40 */ 41 registerListener: function(type, callback) { 42 var callbackKey = BBQUtil.generateGUID(); 43 this._getCallbacks(type).set(callbackKey, callback); 44 45 return callbackKey; 46 }, 47 48 /** 49 * Similar to Watchable.registerListener except the callback will only be called once 50 * 51 * @example 52 * <pre><code type="language-javascript"> 53 * this._dropDown.registerOneTimeListener("onchange", this._subTypeChanged.bind(this)); 54 * </code></pre> 55 * @param {String} type 56 * @param {Function} callback 57 */ 58 registerOneTimeListener: function(type, callback) { 59 callback.__oneTime = true; 60 61 return this.registerListener(type, callback); 62 }, 63 64 /** 65 * @param {String} type 66 * @param {Function} callback 67 */ 68 deRegisterListener: function(type, callback) { 69 if(callbackKey) { 70 return this._getCallbacks(type).unset(callback) ? true : false; 71 } 72 73 return false; 74 }, 75 76 /** 77 * Notifies this object's listeners of an event. 78 * 79 * <pre><code type="language-javascript"> 80 * watchable.registerListener("myEvent", function() { 81 * alert("hello"); 82 * }); 83 * 84 * watchable.notifyListeners("myEvent"); 85 * // alerts "hello" 86 * </code></pre> 87 * 88 * By default the watchable is passed as the first argument to the callback function. If you wish to pass extra arguments, do the following: 89 * 90 * <pre><code type="language-javascript"> 91 * watchable.registerListener("myEvent", function(theWatchable, someArg) { 92 * alert(someArg); 93 * }); 94 * 95 * watchable.notifyListeners("myEvent", "bob"); 96 * // alerts "bob" 97 * </code></pre> 98 * 99 * @param {String} type 100 * @param {...} args 101 */ 102 notifyListeners: function() { 103 var args = [this]; 104 var type = ""; 105 106 for(var i = 0; i < arguments.length; i++) { 107 if(i == 0) { 108 type = arguments[i]; 109 } else { 110 args.push(arguments[i]); 111 } 112 } 113 114 if(Object.isUndefined(this._getCallbacks(type))) { 115 return; 116 } 117 118 this._getCallbacks(type).keys().each(function(key) { 119 this.notifyListener(type, key, args); 120 }.bind(this)); 121 122 // notify global listeners 123 bbq.lang.Watchable.notifyGlobalListeners.apply(this, arguments); 124 }, 125 126 /** 127 * Calls the passed callback and deregisters it if it's a one time listener. 128 * 129 * @param {String} type The event type 130 * @param {String} key The callback key 131 * @param {Array} args Arguments to pass to the callback 132 */ 133 notifyListener: function(type, key, args) { 134 var callback = this._getCallbacks(type).get(key); 135 136 if(!callback) { 137 return; 138 } 139 140 try { 141 callback.apply(this, args); 142 } catch(e) { 143 Log.error("Error invoking callback " + type + " key " + key, e); 144 } 145 146 if(callback.__oneTime) { 147 this.deRegisterListener(type, key); 148 } 149 } 150 }); 151 152 /** 153 * Storage for global callbacks 154 */ 155 bbq.lang.Watchable._globalCallbacks = {}; 156 157 bbq.lang.Watchable._getGlobalCallbacks = function(type) { 158 if(!Object.isArray(bbq.lang.Watchable._globalCallbacks[type])) { 159 bbq.lang.Watchable._globalCallbacks[type] = []; 160 } 161 162 return bbq.lang.Watchable._globalCallbacks[type]; 163 } 164 165 /** 166 * Allows us to register for any type of callback called on any object 167 * 168 * @param {String} type 169 * @param {Function} callback 170 */ 171 bbq.lang.Watchable.registerGlobalListener = function(type, callback) { 172 bbq.lang.Watchable._getGlobalCallbacks(type).push(callback); 173 }; 174 175 /** 176 * Notifies global listeners. 177 */ 178 bbq.lang.Watchable.notifyGlobalListeners = function() { 179 var args = [this]; 180 var type = ""; 181 182 for(var i = 0; i < arguments.length; i++) { 183 if(i == 0) { 184 type = arguments[i]; 185 } else { 186 args.push(arguments[i]); 187 } 188 } 189 190 var callbacks = bbq.lang.Watchable._getGlobalCallbacks(type); 191 192 for(var i = 0; i < callbacks.length; i++) { 193 try { 194 callbacks[i].apply(this, args); 195 } catch(e) { 196 Log.error("Error invoking callback on global listeners", e); 197 } 198 } 199 }; 200