Path: blob/trunk/third_party/closure/goog/events/eventhandler.js
2868 views
// Copyright 2005 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Class to create objects which want to handle multiple events16* and have their listeners easily cleaned up via a dispose method.17*18* Example:19* <pre>20* function Something() {21* Something.base(this);22*23* ... set up object ...24*25* // Add event listeners26* this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);27* this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);28* this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);29* this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);30* this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);31* }32* goog.inherits(Something, goog.events.EventHandler);33*34* Something.prototype.disposeInternal = function() {35* Something.base(this, 'disposeInternal');36* goog.dom.removeNode(this.container);37* };38*39*40* // Then elsewhere:41*42* var activeSomething = null;43* function openSomething() {44* activeSomething = new Something();45* }46*47* function closeSomething() {48* if (activeSomething) {49* activeSomething.dispose(); // Remove event listeners50* activeSomething = null;51* }52* }53* </pre>54*55*/5657goog.provide('goog.events.EventHandler');5859goog.require('goog.Disposable');60goog.require('goog.events');61goog.require('goog.object');6263goog.forwardDeclare('goog.events.EventWrapper');64656667/**68* Super class for objects that want to easily manage a number of event69* listeners. It allows a short cut to listen and also provides a quick way70* to remove all events listeners belonging to this object.71* @param {SCOPE=} opt_scope Object in whose scope to call the listeners.72* @constructor73* @extends {goog.Disposable}74* @template SCOPE75*/76goog.events.EventHandler = function(opt_scope) {77goog.Disposable.call(this);78// TODO(mknichel): Rename this to this.scope_ and fix the classes in google379// that access this private variable. :(80this.handler_ = opt_scope;8182/**83* Keys for events that are being listened to.84* @type {!Object<!goog.events.Key>}85* @private86*/87this.keys_ = {};88};89goog.inherits(goog.events.EventHandler, goog.Disposable);909192/**93* Utility array used to unify the cases of listening for an array of types94* and listening for a single event, without using recursion or allocating95* an array each time.96* @type {!Array<string>}97* @const98* @private99*/100goog.events.EventHandler.typeArray_ = [];101102103/**104* Listen to an event on a Listenable. If the function is omitted then the105* EventHandler's handleEvent method will be used.106* @param {goog.events.ListenableType} src Event source.107* @param {string|Array<string>|108* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}109* type Event type to listen for or array of event types.110* @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}111* opt_fn Optional callback function to be used as the listener or an object112* with handleEvent function.113* @param {boolean=} opt_capture Optional whether to use capture phase.114* @return {THIS} This object, allowing for chaining of calls.115* @this {THIS}116* @template EVENTOBJ, THIS117*/118goog.events.EventHandler.prototype.listen = function(119src, type, opt_fn, opt_capture) {120var self = /** @type {!goog.events.EventHandler} */ (this);121return self.listen_(src, type, opt_fn, opt_capture);122};123124125/**126* Listen to an event on a Listenable. If the function is omitted then the127* EventHandler's handleEvent method will be used.128* @param {goog.events.ListenableType} src Event source.129* @param {string|Array<string>|130* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}131* type Event type to listen for or array of event types.132* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|133* null|undefined} fn Optional callback function to be used as the134* listener or an object with handleEvent function.135* @param {boolean|undefined} capture Optional whether to use capture phase.136* @param {T} scope Object in whose scope to call the listener.137* @return {THIS} This object, allowing for chaining of calls.138* @this {THIS}139* @template T, EVENTOBJ, THIS140*/141goog.events.EventHandler.prototype.listenWithScope = function(142src, type, fn, capture, scope) {143var self = /** @type {!goog.events.EventHandler} */ (this);144// TODO(mknichel): Deprecate this function.145return self.listen_(src, type, fn, capture, scope);146};147148149/**150* Listen to an event on a Listenable. If the function is omitted then the151* EventHandler's handleEvent method will be used.152* @param {goog.events.ListenableType} src Event source.153* @param {string|Array<string>|154* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}155* type Event type to listen for or array of event types.156* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn157* Optional callback function to be used as the listener or an object with158* handleEvent function.159* @param {boolean=} opt_capture Optional whether to use capture phase.160* @param {Object=} opt_scope Object in whose scope to call the listener.161* @return {THIS} This object, allowing for chaining of calls.162* @this {THIS}163* @template EVENTOBJ, THIS164* @private165*/166goog.events.EventHandler.prototype.listen_ = function(167src, type, opt_fn, opt_capture, opt_scope) {168var self = /** @type {!goog.events.EventHandler} */ (this);169if (!goog.isArray(type)) {170if (type) {171goog.events.EventHandler.typeArray_[0] = type.toString();172}173type = goog.events.EventHandler.typeArray_;174}175for (var i = 0; i < type.length; i++) {176var listenerObj = goog.events.listen(177src, type[i], opt_fn || self.handleEvent, opt_capture || false,178opt_scope || self.handler_ || self);179180if (!listenerObj) {181// When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT182// (goog.events.CaptureSimulationMode) in IE8-, it will return null183// value.184return self;185}186187var key = listenerObj.key;188self.keys_[key] = listenerObj;189}190191return self;192};193194195/**196* Listen to an event on a Listenable. If the function is omitted, then the197* EventHandler's handleEvent method will be used. After the event has fired the198* event listener is removed from the target. If an array of event types is199* provided, each event type will be listened to once.200* @param {goog.events.ListenableType} src Event source.201* @param {string|Array<string>|202* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}203* type Event type to listen for or array of event types.204* @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}205* opt_fn206* Optional callback function to be used as the listener or an object with207* handleEvent function.208* @param {boolean=} opt_capture Optional whether to use capture phase.209* @return {THIS} This object, allowing for chaining of calls.210* @this {THIS}211* @template EVENTOBJ, THIS212*/213goog.events.EventHandler.prototype.listenOnce = function(214src, type, opt_fn, opt_capture) {215var self = /** @type {!goog.events.EventHandler} */ (this);216return self.listenOnce_(src, type, opt_fn, opt_capture);217};218219220/**221* Listen to an event on a Listenable. If the function is omitted, then the222* EventHandler's handleEvent method will be used. After the event has fired the223* event listener is removed from the target. If an array of event types is224* provided, each event type will be listened to once.225* @param {goog.events.ListenableType} src Event source.226* @param {string|Array<string>|227* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}228* type Event type to listen for or array of event types.229* @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|230* null|undefined} fn Optional callback function to be used as the231* listener or an object with handleEvent function.232* @param {boolean|undefined} capture Optional whether to use capture phase.233* @param {T} scope Object in whose scope to call the listener.234* @return {THIS} This object, allowing for chaining of calls.235* @this {THIS}236* @template T, EVENTOBJ, THIS237*/238goog.events.EventHandler.prototype.listenOnceWithScope = function(239src, type, fn, capture, scope) {240var self = /** @type {!goog.events.EventHandler} */ (this);241// TODO(mknichel): Deprecate this function.242return self.listenOnce_(src, type, fn, capture, scope);243};244245246/**247* Listen to an event on a Listenable. If the function is omitted, then the248* EventHandler's handleEvent method will be used. After the event has fired249* the event listener is removed from the target. If an array of event types is250* provided, each event type will be listened to once.251* @param {goog.events.ListenableType} src Event source.252* @param {string|Array<string>|253* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}254* type Event type to listen for or array of event types.255* @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn256* Optional callback function to be used as the listener or an object with257* handleEvent function.258* @param {boolean=} opt_capture Optional whether to use capture phase.259* @param {Object=} opt_scope Object in whose scope to call the listener.260* @return {THIS} This object, allowing for chaining of calls.261* @this {THIS}262* @template EVENTOBJ, THIS263* @private264*/265goog.events.EventHandler.prototype.listenOnce_ = function(266src, type, opt_fn, opt_capture, opt_scope) {267var self = /** @type {!goog.events.EventHandler} */ (this);268if (goog.isArray(type)) {269for (var i = 0; i < type.length; i++) {270self.listenOnce_(src, type[i], opt_fn, opt_capture, opt_scope);271}272} else {273var listenerObj = goog.events.listenOnce(274src, type, opt_fn || self.handleEvent, opt_capture,275opt_scope || self.handler_ || self);276if (!listenerObj) {277// When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT278// (goog.events.CaptureSimulationMode) in IE8-, it will return null279// value.280return self;281}282283var key = listenerObj.key;284self.keys_[key] = listenerObj;285}286287return self;288};289290291/**292* Adds an event listener with a specific event wrapper on a DOM Node or an293* object that has implemented {@link goog.events.EventTarget}. A listener can294* only be added once to an object.295*296* @param {EventTarget|goog.events.EventTarget} src The node to listen to297* events on.298* @param {goog.events.EventWrapper} wrapper Event wrapper to use.299* @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener300* Callback method, or an object with a handleEvent function.301* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to302* false).303* @return {THIS} This object, allowing for chaining of calls.304* @this {THIS}305* @template THIS306*/307goog.events.EventHandler.prototype.listenWithWrapper = function(308src, wrapper, listener, opt_capt) {309var self = /** @type {!goog.events.EventHandler} */ (this);310// TODO(mknichel): Remove the opt_scope from this function and then311// templatize it.312return self.listenWithWrapper_(src, wrapper, listener, opt_capt);313};314315316/**317* Adds an event listener with a specific event wrapper on a DOM Node or an318* object that has implemented {@link goog.events.EventTarget}. A listener can319* only be added once to an object.320*321* @param {EventTarget|goog.events.EventTarget} src The node to listen to322* events on.323* @param {goog.events.EventWrapper} wrapper Event wrapper to use.324* @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}325* listener Optional callback function to be used as the326* listener or an object with handleEvent function.327* @param {boolean|undefined} capture Optional whether to use capture phase.328* @param {T} scope Object in whose scope to call the listener.329* @return {THIS} This object, allowing for chaining of calls.330* @this {THIS}331* @template T, THIS332*/333goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(334src, wrapper, listener, capture, scope) {335var self = /** @type {!goog.events.EventHandler} */ (this);336// TODO(mknichel): Deprecate this function.337return self.listenWithWrapper_(src, wrapper, listener, capture, scope);338};339340341/**342* Adds an event listener with a specific event wrapper on a DOM Node or an343* object that has implemented {@link goog.events.EventTarget}. A listener can344* only be added once to an object.345*346* @param {EventTarget|goog.events.EventTarget} src The node to listen to347* events on.348* @param {goog.events.EventWrapper} wrapper Event wrapper to use.349* @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback350* method, or an object with a handleEvent function.351* @param {boolean=} opt_capt Whether to fire in capture phase (defaults to352* false).353* @param {Object=} opt_scope Element in whose scope to call the listener.354* @return {THIS} This object, allowing for chaining of calls.355* @this {THIS}356* @template THIS357* @private358*/359goog.events.EventHandler.prototype.listenWithWrapper_ = function(360src, wrapper, listener, opt_capt, opt_scope) {361var self = /** @type {!goog.events.EventHandler} */ (this);362wrapper.listen(363src, listener, opt_capt, opt_scope || self.handler_ || self, self);364return self;365};366367368/**369* @return {number} Number of listeners registered by this handler.370*/371goog.events.EventHandler.prototype.getListenerCount = function() {372var count = 0;373for (var key in this.keys_) {374if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {375count++;376}377}378return count;379};380381382/**383* Unlistens on an event.384* @param {goog.events.ListenableType} src Event source.385* @param {string|Array<string>|386* !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}387* type Event type or array of event types to unlisten to.388* @param {function(this:?, EVENTOBJ):?|{handleEvent:function(?):?}|null=}389* opt_fn Optional callback function to be used as the listener or an object390* with handleEvent function.391* @param {boolean=} opt_capture Optional whether to use capture phase.392* @param {Object=} opt_scope Object in whose scope to call the listener.393* @return {THIS} This object, allowing for chaining of calls.394* @this {THIS}395* @template EVENTOBJ, THIS396*/397goog.events.EventHandler.prototype.unlisten = function(398src, type, opt_fn, opt_capture, opt_scope) {399var self = /** @type {!goog.events.EventHandler} */ (this);400if (goog.isArray(type)) {401for (var i = 0; i < type.length; i++) {402self.unlisten(src, type[i], opt_fn, opt_capture, opt_scope);403}404} else {405var listener = goog.events.getListener(406src, type, opt_fn || self.handleEvent, opt_capture,407opt_scope || self.handler_ || self);408409if (listener) {410goog.events.unlistenByKey(listener);411delete self.keys_[listener.key];412}413}414415return self;416};417418419/**420* Removes an event listener which was added with listenWithWrapper().421*422* @param {EventTarget|goog.events.EventTarget} src The target to stop423* listening to events on.424* @param {goog.events.EventWrapper} wrapper Event wrapper to use.425* @param {function(?):?|{handleEvent:function(?):?}|null} listener The426* listener function to remove.427* @param {boolean=} opt_capt In DOM-compliant browsers, this determines428* whether the listener is fired during the capture or bubble phase of the429* event.430* @param {Object=} opt_scope Element in whose scope to call the listener.431* @return {THIS} This object, allowing for chaining of calls.432* @this {THIS}433* @template THIS434*/435goog.events.EventHandler.prototype.unlistenWithWrapper = function(436src, wrapper, listener, opt_capt, opt_scope) {437var self = /** @type {!goog.events.EventHandler} */ (this);438wrapper.unlisten(439src, listener, opt_capt, opt_scope || self.handler_ || self, self);440return self;441};442443444/**445* Unlistens to all events.446*/447goog.events.EventHandler.prototype.removeAll = function() {448goog.object.forEach(this.keys_, function(listenerObj, key) {449if (this.keys_.hasOwnProperty(key)) {450goog.events.unlistenByKey(listenerObj);451}452}, this);453454this.keys_ = {};455};456457458/**459* Disposes of this EventHandler and removes all listeners that it registered.460* @override461* @protected462*/463goog.events.EventHandler.prototype.disposeInternal = function() {464goog.events.EventHandler.superClass_.disposeInternal.call(this);465this.removeAll();466};467468469/**470* Default event handler471* @param {goog.events.Event} e Event object.472*/473goog.events.EventHandler.prototype.handleEvent = function(e) {474throw Error('EventHandler.handleEvent not implemented');475};476477478