// 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');
};