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