Path: blob/trunk/third_party/closure/goog/events/eventtarget.js
4198 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A disposable implementation of a custom8* listenable/event target. See also: documentation for9* `goog.events.Listenable`.10*11* @see ../demos/eventtarget.html12* @see goog.events.Listenable13*/1415goog.provide('goog.events.EventTarget');1617goog.require('goog.Disposable');18goog.require('goog.asserts');19goog.require('goog.events');20goog.require('goog.events.Event');21goog.require('goog.events.Listenable');22goog.require('goog.events.ListenerMap');23goog.require('goog.object');24goog.requireType('goog.events.EventId');25goog.requireType('goog.events.EventLike');26goog.requireType('goog.events.ListenableKey');27282930/**31* An implementation of `goog.events.Listenable` with full W3C32* EventTarget-like support (capture/bubble mechanism, stopping event33* propagation, preventing default actions).34*35* You may subclass this class to turn your class into a Listenable.36*37* Unless propagation is stopped, an event dispatched by an38* EventTarget will bubble to the parent returned by39* `getParentEventTarget`. To set the parent, call40* `setParentEventTarget`. Subclasses that don't support41* changing the parent can override the setter to throw an error.42*43* Example usage:44* <pre>45* var source = new goog.events.EventTarget();46* function handleEvent(e) {47* alert('Type: ' + e.type + '; Target: ' + e.target);48* }49* source.listen('foo', handleEvent);50* // Or: goog.events.listen(source, 'foo', handleEvent);51* ...52* source.dispatchEvent('foo'); // will call handleEvent53* ...54* source.unlisten('foo', handleEvent);55* // Or: goog.events.unlisten(source, 'foo', handleEvent);56* </pre>57*58* @constructor59* @extends {goog.Disposable}60* @implements {goog.events.Listenable}61*/62goog.events.EventTarget = function() {63'use strict';64goog.Disposable.call(this);6566/**67* Maps of event type to an array of listeners.68* @private {!goog.events.ListenerMap}69*/70this.eventTargetListeners_ = new goog.events.ListenerMap(this);7172/**73* The object to use for event.target. Useful when mixing in an74* EventTarget to another object.75* @private {!Object}76*/77this.actualEventTarget_ = this;7879/**80* Parent event target, used during event bubbling.81*82* TODO(chrishenry): Change this to goog.events.Listenable. This83* currently breaks people who expect getParentEventTarget to return84* goog.events.EventTarget.85*86* @private {?goog.events.EventTarget}87*/88this.parentEventTarget_ = null;89};90goog.inherits(goog.events.EventTarget, goog.Disposable);91goog.events.Listenable.addImplementation(goog.events.EventTarget);929394/**95* An artificial cap on the number of ancestors you can have. This is mainly96* for loop detection.97* @const {number}98* @private99*/100goog.events.EventTarget.MAX_ANCESTORS_ = 1000;101102103/**104* Returns the parent of this event target to use for bubbling.105*106* @return {goog.events.EventTarget} The parent EventTarget or null if107* there is no parent.108* @override109*/110goog.events.EventTarget.prototype.getParentEventTarget = function() {111'use strict';112return this.parentEventTarget_;113};114115116/**117* Sets the parent of this event target to use for capture/bubble118* mechanism.119* @param {goog.events.EventTarget} parent Parent listenable (null if none).120*/121goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {122'use strict';123this.parentEventTarget_ = parent;124};125126127/**128* Adds an event listener to the event target. The same handler can only be129* added once per the type. Even if you add the same handler multiple times130* using the same type then it will only be called once when the event is131* dispatched.132*133* @param {string|!goog.events.EventId} type The type of the event to listen for134* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function135* to handle the event. The handler can also be an object that implements136* the handleEvent method which takes the event object as argument.137* @param {boolean=} opt_capture In DOM-compliant browsers, this determines138* whether the listener is fired during the capture or bubble phase139* of the event.140* @param {Object=} opt_handlerScope Object in whose scope to call141* the listener.142* @deprecated Use `#listen` instead, when possible. Otherwise, use143* `goog.events.listen` if you are passing Object144* (instead of Function) as handler.145*/146goog.events.EventTarget.prototype.addEventListener = function(147type, handler, opt_capture, opt_handlerScope) {148'use strict';149goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);150};151152153/**154* Removes an event listener from the event target. The handler must be the155* same object as the one added. If the handler has not been added then156* nothing is done.157*158* @param {string|!goog.events.EventId} type The type of the event to listen for159* @param {function(?):?|{handleEvent:function(?):?}|null} handler The function160* to handle the event. The handler can also be an object that implements161* the handleEvent method which takes the event object as argument.162* @param {boolean=} opt_capture In DOM-compliant browsers, this determines163* whether the listener is fired during the capture or bubble phase164* of the event.165* @param {Object=} opt_handlerScope Object in whose scope to call166* the listener.167* @deprecated Use `#unlisten` instead, when possible. Otherwise, use168* `goog.events.unlisten` if you are passing Object169* (instead of Function) as handler.170*/171goog.events.EventTarget.prototype.removeEventListener = function(172type, handler, opt_capture, opt_handlerScope) {173'use strict';174goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);175};176177178/**179* @param {?goog.events.EventLike} e Event object.180* @return {boolean} If anyone called preventDefault on the event object (or181* if any of the listeners returns false) this will also return false.182* @override183*/184goog.events.EventTarget.prototype.dispatchEvent = function(e) {185'use strict';186this.assertInitialized_();187188var ancestorsTree, ancestor = this.getParentEventTarget();189if (ancestor) {190ancestorsTree = [];191var ancestorCount = 1;192for (; ancestor; ancestor = ancestor.getParentEventTarget()) {193ancestorsTree.push(ancestor);194goog.asserts.assert(195(++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),196'infinite loop');197}198}199200return goog.events.EventTarget.dispatchEventInternal_(201this.actualEventTarget_, e, ancestorsTree);202};203204205/**206* Removes listeners from this object. Classes that extend EventTarget may207* need to override this method in order to remove references to DOM Elements208* and additional listeners.209* @override210* @protected211*/212goog.events.EventTarget.prototype.disposeInternal = function() {213'use strict';214goog.events.EventTarget.superClass_.disposeInternal.call(this);215216this.removeAllListeners();217this.parentEventTarget_ = null;218};219220221/**222* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.223* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback224* method.225* @param {boolean=} opt_useCapture Whether to fire in capture phase226* (defaults to false).227* @param {SCOPE=} opt_listenerScope Object in whose scope to call the228* listener.229* @return {!goog.events.ListenableKey} Unique key for the listener.230* @template SCOPE,EVENTOBJ231* @override232*/233goog.events.EventTarget.prototype.listen = function(234type, listener, opt_useCapture, opt_listenerScope) {235'use strict';236this.assertInitialized_();237return this.eventTargetListeners_.add(238String(type), listener, false /* callOnce */, opt_useCapture,239opt_listenerScope);240};241242243/**244* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.245* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback246* method.247* @param {boolean=} opt_useCapture Whether to fire in capture phase248* (defaults to false).249* @param {SCOPE=} opt_listenerScope Object in whose scope to call the250* listener.251* @return {!goog.events.ListenableKey} Unique key for the listener.252* @template SCOPE,EVENTOBJ253* @override254*/255goog.events.EventTarget.prototype.listenOnce = function(256type, listener, opt_useCapture, opt_listenerScope) {257'use strict';258return this.eventTargetListeners_.add(259String(type), listener, true /* callOnce */, opt_useCapture,260opt_listenerScope);261};262263264/**265* @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.266* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback267* method.268* @param {boolean=} opt_useCapture Whether to fire in capture phase269* (defaults to false).270* @param {SCOPE=} opt_listenerScope Object in whose scope to call271* the listener.272* @return {boolean} Whether any listener was removed.273* @template SCOPE,EVENTOBJ274* @override275*/276goog.events.EventTarget.prototype.unlisten = function(277type, listener, opt_useCapture, opt_listenerScope) {278'use strict';279return this.eventTargetListeners_.remove(280String(type), listener, opt_useCapture, opt_listenerScope);281};282283284/**285* @param {!goog.events.ListenableKey} key The key returned by286* listen() or listenOnce().287* @return {boolean} Whether any listener was removed.288* @override289*/290goog.events.EventTarget.prototype.unlistenByKey = function(key) {291'use strict';292return this.eventTargetListeners_.removeByKey(key);293};294295296/**297* @param {string|!goog.events.EventId=} opt_type Type of event to remove,298* default is to remove all types.299* @return {number} Number of listeners removed.300* @override301*/302goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {303'use strict';304// TODO(chrishenry): Previously, removeAllListeners can be called on305// uninitialized EventTarget, so we preserve that behavior. We306// should remove this when usages that rely on that fact are purged.307if (!this.eventTargetListeners_) {308return 0;309}310return this.eventTargetListeners_.removeAll(opt_type);311};312313314/**315* @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the316* listeners to fire.317* @param {boolean} capture The capture mode of the listeners to fire.318* @param {EVENTOBJ} eventObject The event object to fire.319* @return {boolean} Whether all listeners succeeded without320* attempting to prevent default behavior. If any listener returns321* false or called goog.events.Event#preventDefault, this returns322* false.323* @template EVENTOBJ324* @override325*/326goog.events.EventTarget.prototype.fireListeners = function(327type, capture, eventObject) {328'use strict';329// TODO(chrishenry): Original code avoids array creation when there330// is no listener, so we do the same. If this optimization turns331// out to be not required, we can replace this with332// getListeners(type, capture) instead, which is simpler.333var listenerArray = this.eventTargetListeners_.listeners[String(type)];334if (!listenerArray) {335return true;336}337listenerArray = listenerArray.concat();338339var rv = true;340for (var i = 0; i < listenerArray.length; ++i) {341var listener = listenerArray[i];342// We might not have a listener if the listener was removed.343if (listener && !listener.removed && listener.capture == capture) {344var listenerFn = listener.listener;345var listenerHandler = listener.handler || listener.src;346347if (listener.callOnce) {348this.unlistenByKey(listener);349}350rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;351}352}353354return rv && !eventObject.defaultPrevented;355};356357358/**359* @param {string|!goog.events.EventId} type The type of the listeners to fire.360* @param {boolean} capture The capture mode of the listeners to fire.361* @return {!Array<!goog.events.ListenableKey>} An array of registered362* listeners.363* @template EVENTOBJ364* @override365*/366goog.events.EventTarget.prototype.getListeners = function(type, capture) {367'use strict';368return this.eventTargetListeners_.getListeners(String(type), capture);369};370371372/**373* @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event374* without the 'on' prefix.375* @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The376* listener function to get.377* @param {boolean} capture Whether the listener is a capturing listener.378* @param {SCOPE=} opt_listenerScope Object in whose scope to call the379* listener.380* @return {?goog.events.ListenableKey} the found listener or null if not found.381* @template SCOPE,EVENTOBJ382* @override383*/384goog.events.EventTarget.prototype.getListener = function(385type, listener, capture, opt_listenerScope) {386'use strict';387return this.eventTargetListeners_.getListener(388String(type), listener, capture, opt_listenerScope);389};390391392/**393* @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.394* @param {boolean=} opt_capture Whether to check for capture or bubble395* listeners.396* @return {boolean} Whether there is any active listeners matching397* the requested type and/or capture phase.398* @template EVENTOBJ399* @override400*/401goog.events.EventTarget.prototype.hasListener = function(402opt_type, opt_capture) {403'use strict';404var id = (opt_type !== undefined) ? String(opt_type) : undefined;405return this.eventTargetListeners_.hasListener(id, opt_capture);406};407408409/**410* Sets the target to be used for `event.target` when firing411* event. Mainly used for testing. For example, see412* `goog.testing.events.mixinListenable`.413* @param {!Object} target The target.414*/415goog.events.EventTarget.prototype.setTargetForTesting = function(target) {416'use strict';417this.actualEventTarget_ = target;418};419420421/**422* Asserts that the event target instance is initialized properly.423* @private424*/425goog.events.EventTarget.prototype.assertInitialized_ = function() {426'use strict';427goog.asserts.assert(428this.eventTargetListeners_,429'Event target is not initialized. Did you call the superclass ' +430'(goog.events.EventTarget) constructor?');431};432433434/**435* Dispatches the given event on the ancestorsTree.436*437* @param {!Object} target The target to dispatch on.438* @param {goog.events.Event|Object|string} e The event object.439* @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors440* tree of the target, in reverse order from the closest ancestor441* to the root event target. May be null if the target has no ancestor.442* @return {boolean} If anyone called preventDefault on the event object (or443* if any of the listeners returns false) this will also return false.444* @private445*/446goog.events.EventTarget.dispatchEventInternal_ = function(447target, e, opt_ancestorsTree) {448'use strict';449/** @suppress {missingProperties} */450var type = e.type || /** @type {string} */ (e);451452// If accepting a string or object, create a custom event object so that453// preventDefault and stopPropagation work with the event.454if (typeof e === 'string') {455e = new goog.events.Event(e, target);456} else if (!(e instanceof goog.events.Event)) {457var oldEvent = e;458e = new goog.events.Event(type, target);459goog.object.extend(e, oldEvent);460} else {461e.target = e.target || target;462}463464var rv = true, currentTarget;465466// Executes all capture listeners on the ancestors, if any.467if (opt_ancestorsTree) {468for (var i = opt_ancestorsTree.length - 1;469!e.hasPropagationStopped() && i >= 0; i--) {470currentTarget = e.currentTarget = opt_ancestorsTree[i];471rv = currentTarget.fireListeners(type, true, e) && rv;472}473}474475// Executes capture and bubble listeners on the target.476if (!e.hasPropagationStopped()) {477currentTarget = /** @type {?} */ (e.currentTarget = target);478rv = currentTarget.fireListeners(type, true, e) && rv;479if (!e.hasPropagationStopped()) {480rv = currentTarget.fireListeners(type, false, e) && rv;481}482}483484// Executes all bubble listeners on the ancestors, if any.485if (opt_ancestorsTree) {486for (i = 0; !e.hasPropagationStopped() && i < opt_ancestorsTree.length;487i++) {488currentTarget = e.currentTarget = opt_ancestorsTree[i];489rv = currentTarget.fireListeners(type, false, e) && rv;490}491}492493return rv;494};495496497