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.2
  9  * @build 182 (06/11/2011 02:42 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.2'
 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          * Call listener passing arbitrary parameters.
 94          * <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>
 95          * @param {Array} [paramsArr]	Array of parameters that should be passed to the listener
 96          * @return {*} Value returned by the listener.
 97          */
 98         execute : function (paramsArr) {
 99             var r;
100             if (this.active && !!this._listener) {
101                 r = this._listener.apply(this.context, paramsArr);
102                 if (this._isOnce) {
103                     this.detach();
104                 }
105             }
106             return r;
107         },
108 
109         /**
110          * Detach binding from signal.
111          * - alias to: mySignal.remove(myBinding.getListener());
112          * @return {Function} Handler function bound to the signal.
113          */
114         detach : function () {
115             return this._signal.remove(this._listener);
116         },
117 
118         /**
119          * @return {Function} Handler function bound to the signal.
120          */
121         getListener : function () {
122             return this._listener;
123         },
124 
125         /**
126          * Remove binding from signal and destroy any reference to external Objects (destroy SignalBinding object).
127          * <p><strong>IMPORTANT:</strong> calling methods on the binding instance after calling dispose will throw errors.</p>
128          */
129         dispose : function () {
130             this.detach();
131             this._destroy();
132         },
133 
134         /**
135          * Delete instance properties
136          * @private
137          */
138         _destroy : function () {
139             delete this._signal;
140             delete this._listener;
141             delete this.context;
142         },
143 
144         /**
145          * @return {boolean} If SignalBinding will only be executed once.
146          */
147         isOnce : function () {
148             return this._isOnce;
149         },
150 
151         /**
152          * @return {string} String representation of the object.
153          */
154         toString : function () {
155             return '[SignalBinding isOnce: ' + this._isOnce + ', active: ' + this.active + ']';
156         }
157 
158     };
159 
160 
161 /*global signals:true, SignalBinding:false*/
162 
163     // Signal --------------------------------------------------------
164     //================================================================
165 
166     /**
167      * Custom event broadcaster
168      * <br />- inspired by Robert Penner's AS3 Signals.
169      * @author Miller Medeiros
170      * @constructor
171      */
172     signals.Signal = function () {
173         /**
174          * @type Array.<SignalBinding>
175          * @private
176          */
177         this._bindings = [];
178     };
179 
180     signals.Signal.prototype = {
181 
182         /**
183          * @type boolean
184          * @private
185          */
186         _shouldPropagate : true,
187 
188         /**
189          * If Signal is active and should broadcast events.
190          * <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>
191          * @type boolean
192          */
193         active : true,
194 
195         /**
196          * @param {Function} listener
197          * @param {boolean} isOnce
198          * @param {Object} [scope]
199          * @param {Number} [priority]
200          * @return {SignalBinding}
201          * @private
202          */
203         _registerListener : function (listener, isOnce, scope, priority) {
204 
205             if (typeof listener !== 'function') {
206                 throw new Error('listener is a required param of add() and addOnce() and should be a Function.');
207             }
208 
209             var prevIndex = this._indexOfListener(listener),
210                 binding;
211 
212             if (prevIndex !== -1) { //avoid creating a new Binding for same listener if already added to list
213                 binding = this._bindings[prevIndex];
214                 if (binding.isOnce() !== isOnce) {
215                     throw new Error('You cannot add'+ (isOnce? '' : 'Once') +'() then add'+ (!isOnce? '' : 'Once') +'() the same listener without removing the relationship first.');
216                 }
217             } else {
218                 binding = new SignalBinding(this, listener, isOnce, scope, priority);
219                 this._addBinding(binding);
220             }
221 
222             return binding;
223         },
224 
225         /**
226          * @param {Function} binding
227          * @private
228          */
229         _addBinding : function (binding) {
230             //simplified insertion sort
231             var n = this._bindings.length;
232             do { --n; } while (this._bindings[n] && binding._priority <= this._bindings[n]._priority);
233             this._bindings.splice(n + 1, 0, binding);
234         },
235 
236         /**
237          * @param {Function} listener
238          * @return {number}
239          * @private
240          */
241         _indexOfListener : function (listener) {
242             var n = this._bindings.length;
243             while (n--) {
244                 if (this._bindings[n]._listener === listener) {
245                     return n;
246                 }
247             }
248             return -1;
249         },
250 
251         /**
252          * Add a listener to the signal.
253          * @param {Function} listener	Signal handler function.
254          * @param {Object} [scope]	Context on which listener will be executed (object that should represent the `this` variable inside listener function).
255          * @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)
256          * @return {SignalBinding} An Object representing the binding between the Signal and listener.
257          */
258         add : function (listener, scope, priority) {
259             return this._registerListener(listener, false, scope, priority);
260         },
261 
262         /**
263          * Add listener to the signal that should be removed after first execution (will be executed only once).
264          * @param {Function} listener	Signal handler function.
265          * @param {Object} [scope]	Context on which listener will be executed (object that should represent the `this` variable inside listener function).
266          * @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)
267          * @return {SignalBinding} An Object representing the binding between the Signal and listener.
268          */
269         addOnce : function (listener, scope, priority) {
270             return this._registerListener(listener, true, scope, priority);
271         },
272 
273         /**
274          * Remove a single listener from the dispatch queue.
275          * @param {Function} listener	Handler function that should be removed.
276          * @return {Function} Listener handler function.
277          */
278         remove : function (listener) {
279             if (typeof listener !== 'function') {
280                 throw new Error('listener is a required param of remove() and should be a Function.');
281             }
282 
283             var i = this._indexOfListener(listener);
284             if (i !== -1) {
285                 this._bindings[i]._destroy(); //no reason to a SignalBinding exist if it isn't attached to a signal
286                 this._bindings.splice(i, 1);
287             }
288             return listener;
289         },
290 
291         /**
292          * Remove all listeners from the Signal.
293          */
294         removeAll : function () {
295             var n = this._bindings.length;
296             while (n--) {
297                 this._bindings[n]._destroy();
298             }
299             this._bindings.length = 0;
300         },
301 
302         /**
303          * @return {number} Number of listeners attached to the Signal.
304          */
305         getNumListeners : function () {
306             return this._bindings.length;
307         },
308 
309         /**
310          * Stop propagation of the event, blocking the dispatch to next listeners on the queue.
311          * <p><strong>IMPORTANT:</strong> should be called only during signal dispatch, calling it before/after dispatch won't affect signal broadcast.</p>
312          * @see signals.Signal.prototype.disable
313          */
314         halt : function () {
315             this._shouldPropagate = false;
316         },
317 
318         /**
319          * Dispatch/Broadcast Signal to all listeners added to the queue.
320          * @param {...*} [params]	Parameters that should be passed to each handler.
321          */
322         dispatch : function (params) {
323             if (! this.active) {
324                 return;
325             }
326 
327             var paramsArr = Array.prototype.slice.call(arguments),
328                 bindings = this._bindings.slice(), //clone array in case add/remove items during dispatch
329                 n = this._bindings.length;
330 
331             this._shouldPropagate = true; //in case `halt` was called before dispatch or during the previous dispatch.
332 
333             //execute all callbacks until end of the list or until a callback returns `false` or stops propagation
334             //reverse loop since listeners with higher priority will be added at the end of the list
335             do { n--; } while (bindings[n] && this._shouldPropagate && bindings[n].execute(paramsArr) !== false);
336         },
337 
338         /**
339          * Remove all bindings from signal and destroy any reference to external objects (destroy Signal object).
340          * <p><strong>IMPORTANT:</strong> calling any method on the signal instance after calling dispose will throw errors.</p>
341          */
342         dispose : function () {
343             this.removeAll();
344             delete this._bindings;
345         },
346 
347         /**
348          * @return {string} String representation of the object.
349          */
350         toString : function () {
351             return '[Signal active: '+ this.active +' numListeners: '+ this.getNumListeners() +']';
352         }
353 
354     };
355 
356 
357 
358 	global.signals = signals;
359 	
360 }(window || this));
361