1 /*jslint onevar:true, undef:true, newcap:true, regexp:true, bitwise:true, maxerr:50, indent:4, white:false, nomen:false, plusplus:false */
  2 /*global window:false, global:false*/
  3 
  4 /*!!
  5  * JS Signals <http://millermedeiros.github.com/js-signals/>
  6  * Released under the MIT license <http://www.opensource.org/licenses/mit-license.php>
  7  * @author Miller Medeiros <http://millermedeiros.com/>
  8  * @version 0.6.3
  9  * @build 187 (07/11/2011 10:14 AM)
 10  */
 11 (function(global){
 12 	
 13     /**
 14      * @namespace Signals Namespace - Custom event/messaging system based on AS3 Signals
 15      * @name signals
 16      */
 17     var signals = /** @lends signals */{
 18         /**
 19          * Signals Version Number
 20          * @type String
 21          * @const
 22          */
 23         VERSION : '0.6.3'
 24     };
 25 
 26 
 27 
 28     // SignalBinding -------------------------------------------------
 29     //================================================================
 30 
 31     /**
 32      * Object that represents a binding between a Signal and a listener function.
 33      * <br />- <strong>This is an internal constructor and shouldn't be called by regular users.</strong>
 34      * <br />- inspired by Joa Ebert AS3 SignalBinding and Robert Penner's Slot classes.
 35      * @author Miller Medeiros
 36      * @constructor
 37      * @internal
 38      * @name signals.SignalBinding
 39      * @param {signals.Signal} signal	Reference to Signal object that listener is currently bound to.
 40      * @param {Function} listener	Handler function bound to the signal.
 41      * @param {boolean} isOnce	If binding should be executed just once.
 42      * @param {Object} [listenerContext]	Context on which listener will be executed (object that should represent the `this` variable inside listener function).
 43      * @param {Number} [priority]	The priority level of the event listener. (default = 0).
 44      */
 45     function SignalBinding(signal, listener, isOnce, listenerContext, priority) {
 46 
 47         /**
 48          * Handler function bound to the signal.
 49          * @type Function
 50          * @private
 51          */
 52         this._listener = listener;
 53 
 54         /**
 55          * If binding should be executed just once.
 56          * @type boolean
 57          * @private
 58          */
 59         this._isOnce = isOnce;
 60 
 61         /**
 62          * Context on which listener will be executed (object that should represent the `this` variable inside listener function).
 63          * @memberOf signals.SignalBinding.prototype
 64          * @name context
 65          * @type Object|undefined|null
 66          */
 67         this.context = listenerContext;
 68 
 69         /**
 70          * Reference to Signal object that listener is currently bound to.
 71          * @type signals.Signal
 72          * @private
 73          */
 74         this._signal = signal;
 75 
 76         /**
 77          * Listener priority
 78          * @type Number
 79          * @private
 80          */
 81         this._priority = priority || 0;
 82     }
 83 
 84     SignalBinding.prototype = /** @lends signals.SignalBinding.prototype */ {
 85 
 86         /**
 87          * If binding is active and should be executed.
 88          * @type boolean
 89          */
 90         active : true,
 91         
 92         /**
 93          * Default parameters passed to listener during `Signal.dispatch` and `SignalBinding.execute`. (curried parameters)
 94          * @type Array|null
 95          */
 96         params : null,
 97 
 98         /**
 99          * Call listener passing arbitrary parameters.
100          * <p>If binding was added using `Signal.addOnce()` it will be automatically removed from signal dispatch queue, this method is used internally for the signal dispatch.</p>
101          * @param {Array} [paramsArr]	Array of parameters that should be passed to the listener
102          * @return {*} Value returned by the listener.
103          */
104         execute : function (paramsArr) {
105             var handlerReturn, params;
106             if (this.active && !!this._listener) {
107                 params = this.params? this.params.concat(paramsArr) : paramsArr;
108                 handlerReturn = this._listener.apply(this.context, params);
109                 if (this._isOnce) {
110                     this.detach();
111                 }
112             }
113             return handlerReturn;
114         },
115 
116         /**
117          * Detach binding from signal.
118          * - alias to: mySignal.remove(myBinding.getListener());
119          * @return {Function|null} Handler function bound to the signal or `null` if binding was previously detached.
120          */
121         detach : function () {
122             return this.isBound()? this._signal.remove(this._listener) : null;
123         },
124 
125         /**
126          * @return {Boolean} `true` if binding is still bound to the signal and have a listener.
127          */
128         isBound : function () {
129             return (!!this._signal && !!this._listener);
130         },
131 
132         /**
133          * @return {Function} Handler function bound to the signal.
134          */
135         getListener : function () {
136             return this._listener;
137         },
138 
139         /**
140          * Delete instance properties
141          * @private
142          */
143         _destroy : function () {
144             delete this._signal;
145             delete this._listener;
146             delete this.context;
147         },
148 
149         /**
150          * @return {boolean} If SignalBinding will only be executed once.
151          */
152         isOnce : function () {
153             return this._isOnce;
154         },
155 
156         /**
157          * @return {string} String representation of the object.
158          */
159         toString : function () {
160             return '[SignalBinding isOnce: ' + this._isOnce +', isBound: '+ this.isBound() +', active: ' + this.active + ']';
161         }
162 
163     };
164 
165 
166 /*global signals:true, SignalBinding:false*/
167 
168     // Signal --------------------------------------------------------
169     //================================================================
170     
171     function validateListener(listener, fnName) {
172         if (typeof listener !== 'function') {
173             throw new Error( 'listener is a required param of {fn}() and should be a Function.'.replace('{fn}', fnName) );
174         }
175     }
176 
177     /**
178      * Custom event broadcaster
179      * <br />- inspired by Robert Penner's AS3 Signals.
180      * @author Miller Medeiros
181      * @constructor
182      */
183     signals.Signal = function () {
184         /**
185          * @type Array.<SignalBinding>
186          * @private
187          */
188         this._bindings = [];
189     };
190 
191     signals.Signal.prototype = {
192 
193         /**
194          * @type boolean
195          * @private
196          */
197         _shouldPropagate : true,
198 
199         /**
200          * If Signal is active and should broadcast events.
201          * <p><strong>IMPORTANT:</strong> Setting this property during a dispatch will only affect the next dispatch, if you want to stop the propagation of a signal use `halt()` instead.</p>
202          * @type boolean
203          */
204         active : true,
205 
206         /**
207          * @param {Function} listener
208          * @param {boolean} isOnce
209          * @param {Object} [scope]
210          * @param {Number} [priority]
211          * @return {SignalBinding}
212          * @private
213          */
214         _registerListener : function (listener, isOnce, scope, priority) {
215 
216             var prevIndex = this._indexOfListener(listener),
217                 binding;
218 
219             if (prevIndex !== -1) { //avoid creating a new Binding for same listener if already added to list
220                 binding = this._bindings[prevIndex];
221                 if (binding.isOnce() !== isOnce) {
222                     throw new Error('You cannot add'+ (isOnce? '' : 'Once') +'() then add'+ (!isOnce? '' : 'Once') +'() the same listener without removing the relationship first.');
223                 }
224             } else {
225                 binding = new SignalBinding(this, listener, isOnce, scope, priority);
226                 this._addBinding(binding);
227             }
228 
229             return binding;
230         },
231 
232         /**
233          * @param {SignalBinding} binding
234          * @private
235          */
236         _addBinding : function (binding) {
237             //simplified insertion sort
238             var n = this._bindings.length;
239             do { --n; } while (this._bindings[n] && binding._priority <= this._bindings[n]._priority);
240             this._bindings.splice(n + 1, 0, binding);
241         },
242 
243         /**
244          * @param {Function} listener
245          * @return {number}
246          * @private
247          */
248         _indexOfListener : function (listener) {
249             var n = this._bindings.length;
250             while (n--) {
251                 if (this._bindings[n]._listener === listener) {
252                     return n;
253                 }
254             }
255             return -1;
256         },
257 
258         /**
259          * Add a listener to the signal.
260          * @param {Function} listener	Signal handler function.
261          * @param {Object} [scope]	Context on which listener will be executed (object that should represent the `this` variable inside listener function).
262          * @param {Number} [priority]	The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0)
263          * @return {SignalBinding} An Object representing the binding between the Signal and listener.
264          */
265         add : function (listener, scope, priority) {
266             validateListener(listener, 'add');
267             return this._registerListener(listener, false, scope, priority);
268         },
269 
270         /**
271          * Add listener to the signal that should be removed after first execution (will be executed only once).
272          * @param {Function} listener	Signal handler function.
273          * @param {Object} [scope]	Context on which listener will be executed (object that should represent the `this` variable inside listener function).
274          * @param {Number} [priority]	The priority level of the event listener. Listeners with higher priority will be executed before listeners with lower priority. Listeners with same priority level will be executed at the same order as they were added. (default = 0)
275          * @return {SignalBinding} An Object representing the binding between the Signal and listener.
276          */
277         addOnce : function (listener, scope, priority) {
278             validateListener(listener, 'addOnce');
279             return this._registerListener(listener, true, scope, priority);
280         },
281 
282         /**
283          * Remove a single listener from the dispatch queue.
284          * @param {Function} listener	Handler function that should be removed.
285          * @return {Function} Listener handler function.
286          */
287         remove : function (listener) {
288             validateListener(listener, 'remove');
289 
290             var i = this._indexOfListener(listener);
291             if (i !== -1) {
292                 this._bindings[i]._destroy(); //no reason to a SignalBinding exist if it isn't attached to a signal
293                 this._bindings.splice(i, 1);
294             }
295             return listener;
296         },
297 
298         /**
299          * Remove all listeners from the Signal.
300          */
301         removeAll : function () {
302             var n = this._bindings.length;
303             while (n--) {
304                 this._bindings[n]._destroy();
305             }
306             this._bindings.length = 0;
307         },
308 
309         /**
310          * @return {number} Number of listeners attached to the Signal.
311          */
312         getNumListeners : function () {
313             return this._bindings.length;
314         },
315 
316         /**
317          * Stop propagation of the event, blocking the dispatch to next listeners on the queue.
318          * <p><strong>IMPORTANT:</strong> should be called only during signal dispatch, calling it before/after dispatch won't affect signal broadcast.</p>
319          * @see signals.Signal.prototype.disable
320          */
321         halt : function () {
322             this._shouldPropagate = false;
323         },
324 
325         /**
326          * Dispatch/Broadcast Signal to all listeners added to the queue.
327          * @param {...*} [params]	Parameters that should be passed to each handler.
328          */
329         dispatch : function (params) {
330             if (! this.active) {
331                 return;
332             }
333 
334             var paramsArr = Array.prototype.slice.call(arguments),
335                 bindings = this._bindings.slice(), //clone array in case add/remove items during dispatch
336                 n = this._bindings.length;
337 
338             this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch.
339 
340             //execute all callbacks until end of the list or until a callback returns `false` or stops propagation
341             //reverse loop since listeners with higher priority will be added at the end of the list
342             do { n--; } while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false);
343         },
344 
345         /**
346          * Remove all bindings from signal and destroy any reference to external objects (destroy Signal object).
347          * <p><strong>IMPORTANT:</strong> calling any method on the signal instance after calling dispose will throw errors.</p>
348          */
349         dispose : function () {
350             this.removeAll();
351             delete this._bindings;
352         },
353 
354         /**
355          * @return {string} String representation of the object.
356          */
357         toString : function () {
358             return '[Signal active: '+ this.active +' numListeners: '+ this.getNumListeners() +']';
359         }
360 
361     };
362 
363 
364 
365 	global.signals = signals;
366 	
367 }(window || this));
368