#

API Documentation for: NEXT
Show: Inherited Protected Private Deprecated

# File:EventDispatcher.js

    /*
    * EventDispatcher
    * Visit http://createjs.com/ for documentation, updates and examples.
    *
    * Copyright (c) 2010 gskinner.com, inc.
    *
    * Permission is hereby granted, free of charge, to any person
    * obtaining a copy of this software and associated documentation
    * files (the "Software"), to deal in the Software without
    * restriction, including without limitation the rights to use,
    * copy, modify, merge, publish, distribute, sublicense, and/or sell
    * copies of the Software, and to permit persons to whom the
    * Software is furnished to do so, subject to the following
    * conditions:
    *
    * The above copyright notice and this permission notice shall be
    * included in all copies or substantial portions of the Software.
    *
    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    * OTHER DEALINGS IN THE SOFTWARE.
    */

    /**
     * @module CreateJS
     */

    // namespace:
    this.createjs = this.createjs||{};

    (function() {
    	"use strict";

    // constructor:
    	/**
    	 * EventDispatcher provides methods for managing queues of event listeners and dispatching events.
    	 *
    	 * You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the
    	 * EventDispatcher {{#crossLink "EventDispatcher/initialize"}}{{/crossLink}} method.
    	 *
    	 * Together with the CreateJS Event class, EventDispatcher provides an extended event model that is based on the
    	 * DOM Level 2 event model, including addEventListener, removeEventListener, and dispatchEvent. It supports
    	 * bubbling / capture, preventDefault, stopPropagation, stopImmediatePropagation, and handleEvent.
    	 *
    	 * EventDispatcher also exposes a {{#crossLink "EventDispatcher/on"}}{{/crossLink}} method, which makes it easier
    	 * to create scoped listeners, listeners that only run once, and listeners with associated arbitrary data. The
    	 * {{#crossLink "EventDispatcher/off"}}{{/crossLink}} method is merely an alias to
    	 * {{#crossLink "EventDispatcher/removeEventListener"}}{{/crossLink}}.
    	 *
    	 * Another addition to the DOM Level 2 model is the {{#crossLink "EventDispatcher/removeAllEventListeners"}}{{/crossLink}}
    	 * method, which can be used to listeners for all events, or listeners for a specific event. The Event object also
    	 * includes a {{#crossLink "Event/remove"}}{{/crossLink}} method which removes the active listener.
    	 *
    	 * <h4>Example</h4>
    	 * Add EventDispatcher capabilities to the "MyClass" class.
    	 *
    	 *      EventDispatcher.initialize(MyClass.prototype);
    	 *
    	 * Add an event (see {{#crossLink "EventDispatcher/addEventListener"}}{{/crossLink}}).
    	 *
    	 *      instance.addEventListener("eventName", handlerMethod);
    	 *      function handlerMethod(event) {
    	 *          console.log(event.target + " Was Clicked");
    	 *      }
    	 *
    	 * <b>Maintaining proper scope</b><br />
    	 * Scope (ie. "this") can be be a challenge with events. Using the {{#crossLink "EventDispatcher/on"}}{{/crossLink}}
    	 * method to subscribe to events simplifies this.
    	 *
    	 *      instance.addEventListener("click", function(event) {
    	 *          console.log(instance == this); // false, scope is ambiguous.
    	 *      });
    	 *
    	 *      instance.on("click", function(event) {
    	 *          console.log(instance == this); // true, "on" uses dispatcher scope by default.
    	 *      });
    	 *
    	 * If you want to use addEventListener instead, you may want to use function.bind() or a similar proxy to manage
    	 * scope.
    	 *
    	 * <b>Browser support</b>
    	 * The event model in CreateJS can be used separately from the suite in any project, however the inheritance model
    	 * requires modern browsers (IE9+).
    	 *
    	 *
    	 * @class EventDispatcher
    	 * @constructor
    	 **/
    	function EventDispatcher() {

    	// private properties:
    		/**
    		 * @protected
    		 * @property _listeners
    		 * @type Object
    		 **/
    		this._listeners = null;

    		/**
    		 * @protected
    		 * @property _captureListeners
    		 * @type Object
    		 **/
    		this._captureListeners = null;
    	}
    	var p = EventDispatcher.prototype;

    // static public methods:
    	/**
    	 * Static initializer to mix EventDispatcher methods into a target object or prototype.
    	 *
    	 * 		EventDispatcher.initialize(MyClass.prototype); // add to the prototype of the class
    	 * 		EventDispatcher.initialize(myObject); // add to a specific instance
    	 *
    	 * @method initialize
    	 * @static
    	 * @param {Object} target The target object to inject EventDispatcher methods into. This can be an instance or a
    	 * prototype.
    	 **/
    	EventDispatcher.initialize = function(target) {
    		target.addEventListener = p.addEventListener;
    		target.on = p.on;
    		target.removeEventListener = target.off =  p.removeEventListener;
    		target.removeAllEventListeners = p.removeAllEventListeners;
    		target.hasEventListener = p.hasEventListener;
    		target.dispatchEvent = p.dispatchEvent;
    		target._dispatchEvent = p._dispatchEvent;
    		target.willTrigger = p.willTrigger;
    	};

    // public methods:
    	/**
    	 * Adds the specified event listener. Note that adding multiple listeners to the same function will result in
    	 * multiple callbacks getting fired.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 *      displayObject.addEventListener("click", handleClick);
    	 *      function handleClick(event) {
    	 *         // Click happened.
    	 *      }
    	 *
    	 * @method addEventListener
    	 * @param {String} type The string type of the event.
    	 * @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
    	 * the event is dispatched.
    	 * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
    	 * @return {Function | Object} Returns the listener for chaining or assignment.
    	 **/
    	p.addEventListener = function(type, listener, useCapture) {
    		var listeners;
    		if (useCapture) {
    			listeners = this._captureListeners = this._captureListeners||{};
    		} else {
    			listeners = this._listeners = this._listeners||{};
    		}
    		var arr = listeners[type];
    		if (arr) { this.removeEventListener(type, listener, useCapture); }
    		arr = listeners[type]; // remove may have deleted the array
    		if (!arr) { listeners[type] = [listener];  }
    		else { arr.push(listener); }
    		return listener;
    	};

    	/**
    	 * A shortcut method for using addEventListener that makes it easier to specify an execution scope, have a listener
    	 * only run once, associate arbitrary data with the listener, and remove the listener.
    	 *
    	 * This method works by creating an anonymous wrapper function and subscribing it with addEventListener.
    	 * The wrapper function is returned for use with `removeEventListener` (or `off`).
    	 *
    	 * <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener, or use
    	 * {{#crossLink "Event/remove"}}{{/crossLink}}. Likewise, each time you call `on` a NEW wrapper function is subscribed, so multiple calls
    	 * to `on` with the same params will create multiple listeners.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 * 		var listener = myBtn.on("click", handleClick, null, false, {count:3});
    	 * 		function handleClick(evt, data) {
    	 * 			data.count -= 1;
    	 * 			console.log(this == myBtn); // true - scope defaults to the dispatcher
    	 * 			if (data.count == 0) {
    	 * 				alert("clicked 3 times!");
    	 * 				myBtn.off("click", listener);
    	 * 				// alternately: evt.remove();
    	 * 			}
    	 * 		}
    	 *
    	 * @method on
    	 * @param {String} type The string type of the event.
    	 * @param {Function | Object} listener An object with a handleEvent method, or a function that will be called when
    	 * the event is dispatched.
    	 * @param {Object} [scope] The scope to execute the listener in. Defaults to the dispatcher/currentTarget for function listeners, and to the listener itself for object listeners (ie. using handleEvent).
    	 * @param {Boolean} [once=false] If true, the listener will remove itself after the first time it is triggered.
    	 * @param {*} [data] Arbitrary data that will be included as the second parameter when the listener is called.
    	 * @param {Boolean} [useCapture=false] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
    	 * @return {Function} Returns the anonymous function that was created and assigned as the listener. This is needed to remove the listener later using .removeEventListener.
    	 **/
    	p.on = function(type, listener, scope, once, data, useCapture) {
    		if (listener.handleEvent) {
    			scope = scope||listener;
    			listener = listener.handleEvent;
    		}
    		scope = scope||this;
    		return this.addEventListener(type, function(evt) {
    				listener.call(scope, evt, data);
    				once&&evt.remove();
    			}, useCapture);
    	};

    	/**
    	 * Removes the specified event listener.
    	 *
    	 * <b>Important Note:</b> that you must pass the exact function reference used when the event was added. If a proxy
    	 * function, or function closure is used as the callback, the proxy/closure reference must be used - a new proxy or
    	 * closure will not work.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 *      displayObject.removeEventListener("click", handleClick);
    	 *
    	 * @method removeEventListener
    	 * @param {String} type The string type of the event.
    	 * @param {Function | Object} listener The listener function or object.
    	 * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
    	 **/
    	p.removeEventListener = function(type, listener, useCapture) {
    		var listeners = useCapture ? this._captureListeners : this._listeners;
    		if (!listeners) { return; }
    		var arr = listeners[type];
    		if (!arr) { return; }
    		for (var i=0,l=arr.length; i<l; i++) {
    			if (arr[i] == listener) {
    				if (l==1) { delete(listeners[type]); } // allows for faster checks.
    				else { arr.splice(i,1); }
    				break;
    			}
    		}
    	};

    	/**
    	 * A shortcut to the removeEventListener method, with the same parameters and return value. This is a companion to the
    	 * .on method.
    	 *
    	 * <b>IMPORTANT:</b> To remove a listener added with `on`, you must pass in the returned wrapper function as the listener. See
    	 * {{#crossLink "EventDispatcher/on"}}{{/crossLink}} for an example.
    	 *
    	 * @method off
    	 * @param {String} type The string type of the event.
    	 * @param {Function | Object} listener The listener function or object.
    	 * @param {Boolean} [useCapture] For events that bubble, indicates whether to listen for the event in the capture or bubbling/target phase.
    	 **/
    	p.off = p.removeEventListener;

    	/**
    	 * Removes all listeners for the specified type, or all listeners of all types.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 *      // Remove all listeners
    	 *      displayObject.removeAllEventListeners();
    	 *
    	 *      // Remove all click listeners
    	 *      displayObject.removeAllEventListeners("click");
    	 *
    	 * @method removeAllEventListeners
    	 * @param {String} [type] The string type of the event. If omitted, all listeners for all types will be removed.
    	 **/
    	p.removeAllEventListeners = function(type) {
    		if (!type) { this._listeners = this._captureListeners = null; }
    		else {
    			if (this._listeners) { delete(this._listeners[type]); }
    			if (this._captureListeners) { delete(this._captureListeners[type]); }
    		}
    	};

    	/**
    	 * Dispatches the specified event to all listeners.
    	 *
    	 * <h4>Example</h4>
    	 *
    	 *      // Use a string event
    	 *      this.dispatchEvent("complete");
    	 *
    	 *      // Use an Event instance
    	 *      var event = new createjs.Event("progress");
    	 *      this.dispatchEvent(event);
    	 *
    	 * @method dispatchEvent
    	 * @param {Object | String | Event} eventObj An object with a "type" property, or a string type.
    	 * While a generic object will work, it is recommended to use a CreateJS Event instance. If a string is used,
    	 * dispatchEvent will construct an Event instance if necessary with the specified type. This latter approach can
    	 * be used to avoid event object instantiation for non-bubbling events that may not have any listeners.
    	 * @param {Boolean} [bubbles] Specifies the `bubbles` value when a string was passed to eventObj.
    	 * @param {Boolean} [cancelable] Specifies the `cancelable` value when a string was passed to eventObj.
    	 * @return {Boolean} Returns false if `preventDefault()` was called on a cancelable event, true otherwise.
    	 **/
    	p.dispatchEvent = function(eventObj, bubbles, cancelable) {
    		if (typeof eventObj == "string") {
    			// skip everything if there's no listeners and it doesn't bubble:
    			var listeners = this._listeners;
    			if (!bubbles && (!listeners || !listeners[eventObj])) { return true; }
    			eventObj = new createjs.Event(eventObj, bubbles, cancelable);
    		} else if (eventObj.target && eventObj.clone) {
    			// redispatching an active event object, so clone it:
    			eventObj = eventObj.clone();
    		}

    		// TODO: it would be nice to eliminate this. Maybe in favour of evtObj instanceof Event? Or !!evtObj.createEvent
    		try { eventObj.target = this; } catch (e) {} // try/catch allows redispatching of native events

    		if (!eventObj.bubbles || !this.parent) {
    			this._dispatchEvent(eventObj, 2);
    		} else {
    			var top=this, list=[top];
    			while (top.parent) { list.push(top = top.parent); }
    			var i, l=list.length;

    			// capture & atTarget
    			for (i=l-1; i>=0 && !eventObj.propagationStopped; i--) {
    				list[i]._dispatchEvent(eventObj, 1+(i==0));
    			}
    			// bubbling
    			for (i=1; i<l && !eventObj.propagationStopped; i++) {
    				list[i]._dispatchEvent(eventObj, 3);
    			}
    		}
    		return !eventObj.defaultPrevented;
    	};

    	/**
    	 * Indicates whether there is at least one listener for the specified event type.
    	 * @method hasEventListener
    	 * @param {String} type The string type of the event.
    	 * @return {Boolean} Returns true if there is at least one listener for the specified event.
    	 **/
    	p.hasEventListener = function(type) {
    		var listeners = this._listeners, captureListeners = this._captureListeners;
    		return !!((listeners && listeners[type]) || (captureListeners && captureListeners[type]));
    	};

    	/**
    	 * Indicates whether there is at least one listener for the specified event type on this object or any of its
    	 * ancestors (parent, parent's parent, etc). A return value of true indicates that if a bubbling event of the
    	 * specified type is dispatched from this object, it will trigger at least one listener.
    	 *
    	 * This is similar to {{#crossLink "EventDispatcher/hasEventListener"}}{{/crossLink}}, but it searches the entire
    	 * event flow for a listener, not just this object.
    	 * @method willTrigger
    	 * @param {String} type The string type of the event.
    	 * @return {Boolean} Returns `true` if there is at least one listener for the specified event.
    	 **/
    	p.willTrigger = function(type) {
    		var o = this;
    		while (o) {
    			if (o.hasEventListener(type)) { return true; }
    			o = o.parent;
    		}
    		return false;
    	};

    	/**
    	 * @method toString
    	 * @return {String} a string representation of the instance.
    	 **/
    	p.toString = function() {
    		return "[EventDispatcher]";
    	};

    // private methods:
    	/**
    	 * @method _dispatchEvent
    	 * @param {Object | Event} eventObj
    	 * @param {Object} eventPhase
    	 * @protected
    	 **/
    	p._dispatchEvent = function(eventObj, eventPhase) {
    		var l, arr, listeners = (eventPhase <= 2) ? this._captureListeners : this._listeners;
    		if (eventObj && listeners && (arr = listeners[eventObj.type]) && (l=arr.length)) {
    			try { eventObj.currentTarget = this; } catch (e) {}
    			try { eventObj.eventPhase = eventPhase|0; } catch (e) {}
    			eventObj.removed = false;

    			arr = arr.slice(); // to avoid issues with items being removed or added during the dispatch
    			for (var i=0; i<l && !eventObj.immediatePropagationStopped; i++) {
    				var o = arr[i];
    				if (o.handleEvent) { o.handleEvent(eventObj); }
    				else { o(eventObj); }
    				if (eventObj.removed) {
    					this.off(eventObj.type, o, eventPhase==1);
    					eventObj.removed = false;
    				}
    			}
    		}
    		if (eventPhase === 2) { this._dispatchEvent(eventObj, 2.1); }
    	};

    	createjs.EventDispatcher = EventDispatcher;
    }());