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