// Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Copyright 2005 Google Inc. All Rights Reserved. /** * @fileoverview Class to create objects which want to handle multiple events * and have their listeners easily cleaned up via a dispose method. * * Example: * <pre> * function Something() { * goog.events.EventHandler.call(this); * * ... set up object ... * * // Add event listeners * this.listen(this.starEl, 'click', this.handleStar); * this.listen(this.headerEl, 'click', this.expand); * this.listen(this.collapseEl, 'click', this.collapse); * this.listen(this.infoEl, 'mouseover', this.showHover); * this.listen(this.infoEl, 'mouseout', this.hideHover); * } * goog.inherits(Something, goog.events.EventHandler); * * Something.prototype.disposeInternal = function() { * Something.superClass_.disposeInternal.call(this); * goog.dom.removeNode(this.container); * }; * * * // Then elsewhere: * * var activeSomething = null; * function openSomething() { * activeSomething = new Something(); * } * * function closeSomething() { * if (activeSomething) { * activeSomething.dispose(); // Remove event listeners * activeSomething = null; * } * } * </pre> * */ goog.provide('goog.events.EventHandler'); goog.require('goog.Disposable'); goog.require('goog.events'); goog.require('goog.events.EventWrapper'); goog.require('goog.object'); goog.require('goog.structs.SimplePool'); /** * Super class for objects that want to easily manage a number of event * listeners. It allows a short cut to listen and also provides a quick way * to remove all events listeners belonging to this object. It is optimized to * use less objects if only one event is being listened to, but if that's the * case, it may not be worth using the EventHandler anyway. * @param {Object} opt_handler Object in whose scope to call the listeners. * @constructor * @extends {goog.Disposable} */ goog.events.EventHandler = function(opt_handler) { this.handler_ = opt_handler; }; goog.inherits(goog.events.EventHandler, goog.Disposable); /** * Initial count for the keyPool_ * @type {number} */ goog.events.EventHandler.KEY_POOL_INITIAL_COUNT = 0; /** * Max count for the keyPool_ * @type {number} */ goog.events.EventHandler.KEY_POOL_MAX_COUNT = 100; /** * SimplePool to cache the key object. This was implemented to make IE6 * performance better and removed an object allocation in the listen method * when in steady state. * @type {goog.structs.SimplePool} * @private */ goog.events.EventHandler.keyPool_ = new goog.structs.SimplePool( goog.events.EventHandler.KEY_POOL_INITIAL_COUNT, goog.events.EventHandler.KEY_POOL_MAX_COUNT); /** * Keys for events that are being listened to. This is used once there are more * than one event to listen to. If there is only one event to listen to, key_ * is used. * @type {Object?} * @private */ goog.events.EventHandler.keys_ = null; /** * Keys for event that is being listened to if only one event is being listened * to. This is a performance optimization to avoid creating an extra object * if not necessary. * @type {string?} * @private */ goog.events.EventHandler.key_ = null; /** * Listen to an event on a DOM node or EventTarget. If the function is ommitted * then the EventHandler's handleEvent method will be used. * @param {goog.events.EventTarget|EventTarget} src Event source. * @param {string|Array.<string>} type Event type to listen for or array of * event types. * @param {Function|Object} opt_fn Optional callback function to be used as the * listener or an object with handleEvent function. * @param {boolean} opt_capture Optional whether to use capture phase. * @param {Object} opt_handler Object in whose scope to call the listener. * @return {goog.events.EventHandler} This object, allowing for chaining of * calls. */ goog.events.EventHandler.prototype.listen = function(src, type, opt_fn, opt_capture, opt_handler) { if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { this.listen(src, type[i], opt_fn, opt_capture, opt_handler); } } else { var key = (/** @type {number} */ goog.events.listen(src, type, opt_fn || this, opt_capture || false, opt_handler || this.handler_ || this)); this.recordListenerKey_(key); } return this; }; /** * Listen to an event on a DOM node or EventTarget. If the function is ommitted * then the EventHandler's handleEvent method will be used. After the event has * fired the event listener is removed from the target. If an array of event * types is provided, each event type will be listened to once. * @param {goog.events.EventTarget|EventTarget} src Event source. * @param {string|Array.<string>} type Event type to listen for or array of * event types. * @param {Function|Object} opt_fn Optional callback function to be used as the * listener or an object with handleEvent function. * @param {boolean} opt_capture Optional whether to use capture phase. * @param {Object} opt_handler Object in whose scope to call the listener. * @return {goog.events.EventHandler} This object, allowing for chaining of * calls. */ goog.events.EventHandler.prototype.listenOnce = function(src, type, opt_fn, opt_capture, opt_handler) { if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { this.listenOnce(src, type[i], opt_fn, opt_capture, opt_handler); } } else { var key = (/** @type {number} */ goog.events.listenOnce(src, type, opt_fn || this, opt_capture || false, opt_handler || this.handler_ || this)); this.recordListenerKey_(key); } return this; }; /** * Adds an event listener with a specific event wrapper on a DOM Node or an * object that has implemented {@link goog.events.EventTarget}. A listener can * only be added once to an object. * * @param {EventTarget|goog.events.EventTarget} src The node to listen to * events on. * @param {goog.events.EventWrapper} wrapper Event wrapper to use. * @param {Function|Object} listener Callback method, or an object with a * handleEvent function. * @param {boolean} opt_capt Whether to fire in capture phase (defaults to * false). * @param {Object} opt_handler Element in whose scope to call the listener. * @return {goog.events.EventHandler} This object, allowing for chaining of * calls. */ goog.events.EventHandler.prototype.listenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) { wrapper.listen(src, listener, opt_capt, opt_handler || this.handler_, this); return this; }; /** * Record the key returned for the listener so that it can be user later * to remove the listener. * @param {number} key Unique key for the listener. * @private */ goog.events.EventHandler.prototype.recordListenerKey_ = function(key) { if (this.keys_) { // already have multiple keys this.keys_[key] = true; } else if (this.key_) { // going from one key to multiple - must now use object as map this.keys_ = goog.events.EventHandler.keyPool_.getObject(); this.keys_[this.key_] = true; this.key_ = null; this.keys_[key] = true; } else { // first key - can use single key this.key_ = key; } }; /** * Unlistens on an event. * @param {goog.events.EventTarget|EventTarget} src Event source. * @param {string|Array.<string>} type Event type to listen for. * @param {Function|Object} opt_fn Optional callback function to be used as the * listener or an object with handleEvent function. * @param {boolean} opt_capture Optional whether to use capture phase. * @param {Object} opt_handler Object in whose scope to call the listener. * @return {goog.events.EventHandler} This object, allowing for chaining of * calls. */ goog.events.EventHandler.prototype.unlisten = function(src, type, opt_fn, opt_capture, opt_handler) { if (this.key_ || this.keys_) { if (goog.isArray(type)) { for (var i = 0; i < type.length; i++) { this.unlisten(src, type[i], opt_fn, opt_capture, opt_handler); } } else { var listener = goog.events.getListener(src, type, opt_fn || this, opt_capture || false, opt_handler || this.handler_ || this); if (listener) { var key = listener.key; goog.events.unlistenByKey(key); if (this.keys_) { goog.object.remove(this.keys_, key); } else if (this.key_ == key) { this.key_ = null; } } } } return this; }; /** * Removes an event listener which was added with listenWithWrapper(). * * @param {EventTarget|goog.events.EventTarget} src The target to stop * listening to events on. * @param {goog.events.EventWrapper} wrapper Event wrapper to use. * @param {Function|Object} listener The listener function to remove. * @param {boolean} opt_capt In DOM-compliant browsers, this determines * whether the listener is fired during the capture or bubble phase of the * event. * @param {Object} opt_handler Element in whose scope to call the listener. * @return {goog.events.EventHandler} This object, allowing for chaining of * calls. */ goog.events.EventHandler.prototype.unlistenWithWrapper = function(src, wrapper, listener, opt_capt, opt_handler) { wrapper.unlisten(src, listener, opt_capt, opt_handler || this.handler_, this); return this; }; /** * Unlistens to all events. */ goog.events.EventHandler.prototype.removeAll = function() { if (this.keys_) { for (var key in this.keys_) { goog.events.unlistenByKey((/** @type {number} */ key)); // Clean the keys before returning object to the pool. delete this.keys_[key]; } goog.events.EventHandler.keyPool_.releaseObject(this.keys_); this.keys_ = null; } else if (this.key_) { goog.events.unlistenByKey(this.key_); } }; /** * Disposes of this EventHandler and remove all listeners that it registered. */ goog.events.EventHandler.prototype.disposeInternal = function() { goog.events.EventHandler.superClass_.disposeInternal.call(this); this.removeAll(); }; /** * Default event handler * @param {goog.events.Event} e Event object. */ goog.events.EventHandler.prototype.handleEvent = function(e) { throw Error('EventHandler.handleEvent not implemented'); };