1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="object.js"/>
 27 /// <reference path="object_list.js"/>
 28 /// <reference path="event.js"/>
 29 
 30 /**
 31  * A class that can receive events and dispatch events. This class can dispatch
 32  * CreateJS events only to their target CreateJS objects. (This class cannot
 33  * dispatch capture events.)
 34  * @extends {createjs.Object}
 35  * @implements {createjs.Event.Target}
 36  * @constructor
 37  */
 38 createjs.EventDispatcher = function() {
 39   createjs.Object.call(this);
 40 };
 41 createjs.inherits('EventDispatcher', createjs.EventDispatcher, createjs.Object);
 42 
 43 /**
 44  * Representing this dispatcher is in the capture phase.
 45  * @const {number}
 46  */
 47 createjs.EventDispatcher.CAPTURE_PHASE = 1;
 48 
 49 /**
 50  * Representing this dispatcher is in the target phase.
 51  * @const {number}
 52  */
 53 createjs.EventDispatcher.AT_TARGET = 2;
 54 
 55 /**
 56  * Representing this dispatcher is in the bubbling phase.
 57  * @const {number}
 58  */
 59 createjs.EventDispatcher.BUBBLING_PHASE = 3;
 60 
 61 /**
 62  * The event ID of the 'click' event.
 63  * @const {number}
 64  */
 65 createjs.EventDispatcher.CLICK = 1 << 0;
 66 
 67 /**
 68  * The event ID of the 'mousedown' event.
 69  * @const {number}
 70  */
 71 createjs.EventDispatcher.MOUSE_DOWN = 1 << 1;
 72 
 73 /**
 74  * The event ID of the 'pressmove' event.
 75  * @const {number}
 76  */
 77 createjs.EventDispatcher.PRESS_MOVE = 1 << 2;
 78 
 79 /**
 80  * The event ID of the 'pressup' event.
 81  * @const {number}
 82  */
 83 createjs.EventDispatcher.PRESS_UP = 1 << 3;
 84 
 85 /**
 86  * Returns the event ID.
 87  * @param {string} type
 88  * @return {number}
 89  * @private
 90  */
 91 createjs.EventDispatcher.getEventId_ = function(type) {
 92   /// <param type="string" name="type"/>
 93   /// <returns type="number"/>
 94   var EVENTS = {
 95     'click': createjs.EventDispatcher.CLICK,
 96     'mousedown': createjs.EventDispatcher.MOUSE_DOWN,
 97     'pressmove': createjs.EventDispatcher.PRESS_MOVE,
 98     'pressup': createjs.EventDispatcher.PRESS_UP
 99   };
100   return EVENTS[type] || 0;
101 };
102 
103 /**
104  * Adds EventDispatcher methods into a target object.
105  * @param {Object} target
106  * @const
107  */
108 createjs.EventDispatcher.initialize = function(target) {
109   /// <param type="Object" name="target"/>
110   var prototype = createjs.EventDispatcher.prototype;
111   for (var key in prototype) {
112     if (!target[key]) {
113       target[key] = prototype[key];
114     }
115   }
116 };
117 
118 /**
119  * An inner class that represents the context of an event listener. This class
120  * is used by the createjs.EventDispatcher class to execute an event listener.
121  * event listener.
122  * @param {Function|Object} listener
123  * @param {Object} scope
124  * @param {boolean} once
125  * @param {*} data
126  * @constructor
127  */
128 createjs.EventDispatcher.Context = function(listener, scope, once, data) {
129   /// <signature>
130   ///   <param type="Function" name="callback"/>
131   ///   <param type="Object" name="scope"/>
132   ///   <param type="boolean" name="once"/>
133   ///   <param name="data"/>
134   /// </signature>
135   /// <signature>
136   ///   <param type="Object" name="listener"/>
137   ///   <param type="Object" name="scope"/>
138   ///   <param type="boolean" name="once"/>
139   ///   <param name="data"/>
140   /// </signature>
141   /**
142    * The function to be executed when an event dispatcher dispatches an event to
143    * this object.
144    * @type {Function}
145    * @private
146    */
147   this.handleEvent_ = listener['handleEvent'] || listener;
148 
149   /**
150    * The context used in executing this event handler.
151    * @type {Object}
152    * @private
153    */
154   this.scope_ = listener['handleEvent'] ? listener : scope;
155 
156   /**
157    * Whether this object should be removed after dispatching an event to it.
158    * @type {boolean}
159    * @private
160    */
161   this.once_ = once;
162 
163   /**
164    * Application-specific data provided in adding an event listener.
165    * @type {*}
166    * @private
167    */
168   this.data_ = data;
169 };
170 
171 /**
172  * Returns whether this listener is equal to the specified listener.
173  * @param {Function|Object} listener
174  * @return {boolean}
175  */
176 createjs.EventDispatcher.Context.prototype.isEqual = function(listener) {
177   /// <signature>
178   ///   <param type="Function" name="callback"/>
179   ///   <returns type="boolean"/>
180   /// </signature>
181   /// <signature>
182   ///   <param type="Object" name="listener"/>
183   ///   <returns type="boolean"/>
184   /// </signature>
185   if (listener['handleEvent']) {
186     return this.scope_ === listener;
187   }
188   return this.handleEvent_ === listener;
189 };
190 
191 /**
192  * Dispatches an event to this listener.
193  * @param {createjs.Event} event
194  * @return {boolean}
195  */
196 createjs.EventDispatcher.Context.prototype.dispatch = function(event) {
197   /// <param type="createjs.Event" name="event"/>
198   /// <returns type="boolean"/>
199   this.handleEvent_.call(this.scope_, event, this.data_);
200   return event.removed || this.once_;
201 };
202 
203 /**
204  * A class that encapsulates an array that allows applications to add objects to
205  * the list or to remove ones from it while they are iterating it.
206  * @extends {createjs.ObjectList}
207  * @constructor
208  */
209 createjs.EventDispatcher.ObjectList = function() {
210   createjs.ObjectList.call(this);
211 };
212 createjs.inherits('EventDispatcher.ObjectList',
213                   createjs.EventDispatcher.ObjectList,
214                   createjs.ObjectList);
215 
216 /**
217  * Removes an event listener from the context list.
218  * @param {Function|Object} listener
219  * @private
220  */
221 createjs.EventDispatcher.ObjectList.prototype.removeListener_ =
222     function(listener) {
223   /// <signature>
224   ///   <param type="Function" name="callback"/>
225   /// </signature>
226   /// <signature>
227   ///   <param type="Object" name="listener"/>
228   /// </signature>
229   var list = this.getItems();
230   for (var i = 0; i < list.length; ++i) {
231     var item = /** @type {createjs.EventDispatcher.Context} */ (list[i]);
232     if (item.isEqual(listener)) {
233       list.splice(i, 1);
234       return;
235     }
236   }
237 };
238 
239 /**
240  * The parent object.
241  * @type {createjs.EventDispatcher}
242  * @protected
243  */
244 createjs.EventDispatcher.prototype['parent'] = null;
245 
246 /**
247  * Event listeners for bubbling events and at-target ones.
248  * @type {Object.<string,createjs.EventDispatcher.ObjectList>}
249  * @private
250  */
251 createjs.EventDispatcher.prototype.listeners_ = null;
252 
253 /**
254  * A bit-mask representing event types that have event listeners attached to
255  * this object.
256  * @type {number}
257  * @private
258  */
259 createjs.EventDispatcher.prototype.eventTypes_ = 0;
260 
261 /**
262  * Whether this object is dispatching events.
263  * @type {boolean}
264  * @private
265  */
266 createjs.EventDispatcher.prototype.dispatching_ = false;
267 
268 /**
269  * Events to be sent after this object finishes dispatching an event.
270  * @type {Array.<createjs.Event>}
271  * @private
272  */
273 createjs.EventDispatcher.prototype.events_ = null;
274 
275 /**
276  * Returns a mapping table of event listeners.
277  * @return {Object.<string,createjs.EventDispatcher.ObjectList>}
278  * @private
279  */
280 createjs.EventDispatcher.prototype.getListeners_ = function() {
281   /// <param type="boolean" name="capture"/>
282   /// <returns type="Object"/>
283   this.listeners_ = this.listeners_ || {};
284   return this.listeners_;
285 };
286 
287 /**
288  * Dispatches a createjs.Event object to listeners attached to this object.
289  * @param {createjs.Event} event
290  * @param {number} phase
291  * @private
292  */
293 createjs.EventDispatcher.prototype.dispatchEvent_ = function(event, phase) {
294   /// <param type="createjs.Event" name="event"/>
295   /// <param type="number" name="phase"/>
296   /// <var type="createjs.EventDispatcher.ObjectList" name="list"/>
297   var listeners = this.getListeners_();
298   var list = listeners[event.type];
299   if (!list || !list.getLength()) {
300     return;
301   }
302   event.setProperties(this, phase);
303 
304   // When an event listener attached to this object dispatches another event to
305   // this object, save the given event to an array to send it when this object
306   // finishes dispatching the event.
307   if (this.dispatching_) {
308     this.events_ = this.events_ || [];
309     this.events_.push(event);
310     return;
311   }
312 
313   // Lock the context array and dispatch this event. Event handlers may add or
314   // remove event listeners while this object calls them and it causes some
315   // consistency problems, e.g. reading the object of an non-existent index.
316   // To avoid such consistency problems, this loop locks the createjs.ObjectList
317   // object so event dispatchers can change its clone, not the original array.
318   // Cloning an array is not so fast on old devices and it is better to clone an
319   // array only when an event listener adds a listener or removes one.
320   this.dispatching_ = true;
321   var contexts = list.lock();
322   var length = contexts.length;
323   for (var i = 0; i < length && !event.immediatePropagationStopped; ++i) {
324     event.removed = false;
325     var context = contexts[i];
326     var remove = context.dispatch(event);
327     if (remove) {
328       list.removeItem(context);
329     }
330   }
331   list.unlock();
332   this.dispatching_ = false;
333 
334   // Dispatch the events having queued to this object. Event listeners for these
335   // defer-sending events may dispatch more events and this code should keep
336   // dispatching defer-sending events until there are not any more events.
337   while (this.events_) {
338     var events = this.events_;
339     this.events_ = null;
340     for (var i = 0; i < events.length; ++i) {
341       this.dispatchEvent_(events[i], events[i].eventPhase);
342     }
343   }
344 };
345 
346 /**
347  * Dispatches a createjs.Event object to listeners attached to this object and
348  * listeners attached to its ancestors. NOTE: this method does not implement the
349  * capture phase, dispatching an event from the stage to this target. Games do
350  * not use capture events so often that this method removes its implementation.
351  * (For games consisting of thousands of CreateJS objects, it takes long time to
352  * dispatch an event from the stage object to this target on Android browsers.)
353  * @param {createjs.Event} event
354  * @return {boolean}
355  * @protected
356  */
357 createjs.EventDispatcher.prototype.dispatchRawEvent = function(event) {
358   /// <param type="createjs.Event" name="event"/>
359   /// <returns type="boolean"/>
360   // Dispatch this event to the target.
361   this.dispatchEvent_(event, createjs.EventDispatcher.AT_TARGET);
362 
363   // Dispatch this event to the ancestors of this target.
364   if (event.bubbles && this['parent']) {
365     for (var node = this['parent']; node && !event.propagationStopped;
366          node = node['parent']) {
367       node.dispatchEvent_(event, createjs.EventDispatcher.BUBBLING_PHASE);
368     }
369   }
370   event.resetProperties();
371   return event.defaultPrevented;
372 };
373 
374 /**
375  * Dispatches a notification event, which consists only of a type name.
376  * @param {string} type
377  * @return {boolean}
378  * @protected
379  */
380 createjs.EventDispatcher.prototype.dispatchNotification = function(type) {
381   /// <param type="string" name="type"/>
382   /// <returns type="boolean"/>
383   // Dispatch this notification event only if this target has listeners for the
384   // event. (A notification event is an at-target event.)
385   var listeners = this.listeners_;
386   if (!listeners || !listeners[type]) {
387     return false;
388   }
389   return this.dispatchRawEvent(new createjs.Event(type, false, false));
390 };
391 
392 /**
393  * Returns event types that have event listeners.
394  * @return {number}
395  * @protected
396  */
397 createjs.EventDispatcher.prototype.getEventTypes = function() {
398   /// <returns type="number"/>
399   return this.eventTypes_;
400 };
401 
402 /**
403  * Adds the specified event listener. Note that adding multiple listeners to the
404  * same function will result in multiple callbacks getting fired.
405  *
406  * Example
407  *   function handleClick(event) {
408  *      // Click happened.
409  *   }
410  *   displayObject.addEventListener("click", handleClick);
411  *
412  * @param {string} type
413  * @param {Function|Object} listener
414  * @param {boolean=} opt_useCapture
415  * @return {Function|Object}
416  */
417 createjs.EventDispatcher.prototype.addListener =
418     function(type, listener, opt_useCapture) {
419   /// <signature>
420   ///   <param type="string" name="type"/>
421   ///   <param type="Function" name="callback"/>
422   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
423   ///   <returns type="Function"/>
424   /// </signature>
425   /// <signature>
426   ///   <param type="string" name="type"/>
427   ///   <param type="Object" name="listener"/>
428   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
429   ///   <returns type="Object"/>
430   /// </signature>
431   return this.on(type, listener, createjs.global, false, null, opt_useCapture);
432 };
433   
434 /** @override */
435 createjs.EventDispatcher.prototype.on =
436     function(type, listener, opt_scope, opt_once, opt_data, opt_useCapture) {
437   /// <signature>
438   ///   <param type="string" name="type"/>
439   ///   <param type="Function" name="callback"/>
440   ///   <param type="Object" optional="true" name="opt_scope"/>
441   ///   <param type="boolean" optional="true" name="opt_once"/>
442   ///   <param optional="true" name="opt_data"/>
443   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
444   ///   <returns type="Function"/>
445   /// </signature>
446   /// <signature>
447   ///   <param type="string" name="type"/>
448   ///   <param type="Object" name="listener"/>
449   ///   <param type="Object" optional="true" name="opt_scope"/>
450   ///   <param type="boolean" optional="true" name="opt_once"/>
451   ///   <param optional="true" name="opt_data"/>
452   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
453   ///   <returns type="Object"/>
454   /// </signature>
455   if (!listener || opt_useCapture) {
456     return listener;
457   }
458   var scope = opt_scope || this;
459   var once = opt_once || false;
460   var data = opt_data || null;
461   var listeners = this.getListeners_();
462   if (!listeners[type]) {
463     listeners[type] = new createjs.EventDispatcher.ObjectList();
464   }
465   listeners[type].pushItem(
466       new createjs.EventDispatcher.Context(listener, scope, once, data));
467   // Update the bit-mask of event types to avoid hit-testing with display
468   // objects that do not have event listeners.
469   this.eventTypes_ |= createjs.EventDispatcher.getEventId_(type);
470   return listener;
471 };
472 
473 /** @override */
474 createjs.EventDispatcher.prototype.off =
475     function(type, listener, opt_useCapture) {
476   /// <signature>
477   ///   <param type="string" name="type"/>
478   ///   <param type="Function" name="callback"/>
479   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
480   ///   <returns type="Function"/>
481   /// </signature>
482   /// <signature>
483   ///   <param type="string" name="type"/>
484   ///   <param type="Object" name="listener"/>
485   ///   <param type="boolean" optional="true" name="opt_useCapture"/>
486   ///   <returns type="Object"/>
487   /// </signature>
488   if (!listener || opt_useCapture) {
489     return;
490   }
491   var listeners = this.getListeners_();
492   var list = listeners[type];
493   if (!list || !list.getLength()) {
494     return;
495   }
496   list.removeListener_(listener);
497   // Remove the specified event from the bit-mask when this object does not have
498   // listeners to stop hit-testing this object any longer.
499   if (!list.getLength()) {
500     var mask = createjs.EventDispatcher.getEventId_(type);
501     if (mask) {
502       this.eventTypes_ &= ~mask;
503     }
504   }
505 };
506   
507 /**
508  * Removes all listeners for the specified type, or all listeners of all types.
509  *
510  * Example
511  *   // Remove all listeners
512  *   displayObject.removeAllEventListeners();
513  *   // Remove all click listeners
514  *   displayObject.removeAllEventListeners("click");
515  *
516  * @param {string=} opt_type
517  */
518 createjs.EventDispatcher.prototype.removeAllListeners = function(opt_type) {
519   /// <param type="string" optional="true" name="opt_type"/>
520   if (!opt_type) {
521     this.listeners_ = null;
522   } else {
523     if (this.listeners_ && this.listeners_[opt_type]) {
524       this.listeners_[opt_type] = null;
525     }
526   }
527 };
528 
529 /**
530  * Dispatches the specified event to all listeners.
531  *
532  * Example
533  *      // Use a string event
534  *      this.dispatchEvent("complete");
535  *      // Use an Event instance
536  *      var event = new createjs.Event("progress");
537  *      this.dispatchEvent(event);
538  *
539  * @param {Object|string|Event} value
540  * @param {Object=} opt_target
541  * @return {boolean}
542  */
543 createjs.EventDispatcher.prototype.dispatch = function(value, opt_target) {
544   /// <signature>
545   ///   <param type="Object" name="object"/>
546   ///   <param type="Object" optional="true" name="opt_target"/>
547   ///   <returns type="boolean"/>
548   /// </signature>
549   /// <signature>
550   ///   <param type="string" name="type"/>
551   ///   <param type="Object" optional="true" name="opt_target"/>
552   ///   <returns type="boolean"/>
553   /// </signature>
554   /// <signature>
555   ///   <param type="Event" name="event"/>
556   ///   <param type="Object" optional="true" name="opt_target"/>
557   ///   <returns type="boolean"/>
558   /// </signature>
559   var event = null;
560   if (createjs.isString(value)) {
561     /// <var type="string" name="type"/>
562     var type = /** @type {string} */ (value);
563     return this.dispatchNotification(type);
564   } else {
565     event = new createjs.Event(value['type'], false, false);
566   }
567   return this.dispatchRawEvent(event);
568 };
569 
570 /**
571  * Indicates whether there are listeners for the specified event type.
572  * @param {string} type
573  * @return {boolean}
574  */
575 createjs.EventDispatcher.prototype.hasListener = function(type) {
576   /// <param type="string" name="type"/>
577   /// <returns type="boolean"/>
578   if (this.listeners_) {
579     var listeners = this.listeners_[type];
580     if (listeners && listeners.getLength()) {
581       return true;
582     }
583   }
584   return false;
585 };
586   
587 /**
588  * Indicates whether there are listeners for the specified event type on this
589  * object or its ancestors. A return value of true indicates that if a bubbling
590  * event of the specified type is dispatched from this object, it will trigger
591  * at least one listener. This is similar to the
592  * EventDispatcher.hasListener() method but it searches the entire event
593  * flow for a listener, not just this object.
594  * @param {string} type
595  * @return {boolean}
596  */
597 createjs.EventDispatcher.prototype.willTrigger = function(type) {
598   /// <param type="string" name="type"/>
599   /// <returns type="boolean"/>
600   var o = this;
601   while (o) {
602     if (o.hasListener(type)) {
603       return true;
604     }
605     o = o['parent'];
606   }
607   return false;
608 };
609 
610 /**
611  * Returns whether the specified object is either this container or its
612  * descendant.
613  * @param {createjs.EventDispatcher} child
614  * @return {boolean}
615  */
616 createjs.EventDispatcher.prototype.contains = function(child) {
617   /// <param type="createjs.EventDispatcher" name="child"/>
618   /// <returns type="boolean"/>
619   while (child) {
620     if (child === this) {
621       return true;
622     }
623     child = child['parent'];
624   }
625   return false;
626 };
627 
628 // Export the createjs.EventDispatcher object to the global namespace.
629 createjs.exportObject('createjs.EventDispatcher', createjs.EventDispatcher, {
630   // createjs.EventDispatcher methods
631   'addEventListener': createjs.EventDispatcher.prototype.addListener,
632   'on': createjs.EventDispatcher.prototype.on,
633   'removeEventListener': createjs.EventDispatcher.prototype.off,
634   'off': createjs.EventDispatcher.prototype.off,
635   'removeAllEventListeners':
636       createjs.EventDispatcher.prototype.removeAllListeners,
637   'dispatchEvent': createjs.EventDispatcher.prototype.dispatch,
638   'hasEventListener': createjs.EventDispatcher.prototype.hasListener,
639   'willTrigger': createjs.EventDispatcher.prototype.willTrigger,
640   'contains': createjs.EventDispatcher.prototype.contains
641 }, {
642   'initialize': createjs.EventDispatcher.initialize
643 });
644