You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
307 lines
9.5 KiB
307 lines
9.5 KiB
7 years ago
|
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
|
||
|
//
|
||
|
// 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.
|
||
|
|
||
|
/**
|
||
|
* @fileoverview A map of listeners that provides utility functions to
|
||
|
* deal with listeners on an event target. Used by
|
||
|
* {@code goog.events.EventTarget}.
|
||
|
*
|
||
|
* WARNING: Do not use this class from outside goog.events package.
|
||
|
*
|
||
|
* @visibility {//closure/goog/bin/sizetests:__pkg__}
|
||
|
* @visibility {//closure/goog/events:__pkg__}
|
||
|
* @visibility {//closure/goog/labs/events:__pkg__}
|
||
|
*/
|
||
|
|
||
|
goog.provide('goog.events.ListenerMap');
|
||
|
|
||
|
goog.require('goog.array');
|
||
|
goog.require('goog.events.Listener');
|
||
|
goog.require('goog.object');
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Creates a new listener map.
|
||
|
* @param {EventTarget|goog.events.Listenable} src The src object.
|
||
|
* @constructor
|
||
|
* @final
|
||
|
*/
|
||
|
goog.events.ListenerMap = function(src) {
|
||
|
/** @type {EventTarget|goog.events.Listenable} */
|
||
|
this.src = src;
|
||
|
|
||
|
/**
|
||
|
* Maps of event type to an array of listeners.
|
||
|
* @type {!Object<string, !Array<!goog.events.Listener>>}
|
||
|
*/
|
||
|
this.listeners = {};
|
||
|
|
||
|
/**
|
||
|
* The count of types in this map that have registered listeners.
|
||
|
* @private {number}
|
||
|
*/
|
||
|
this.typeCount_ = 0;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @return {number} The count of event types in this map that actually
|
||
|
* have registered listeners.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.getTypeCount = function() {
|
||
|
return this.typeCount_;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @return {number} Total number of registered listeners.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.getListenerCount = function() {
|
||
|
var count = 0;
|
||
|
for (var type in this.listeners) {
|
||
|
count += this.listeners[type].length;
|
||
|
}
|
||
|
return count;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Adds an event listener. A listener can only be added once to an
|
||
|
* object and if it is added again the key for the listener is
|
||
|
* returned.
|
||
|
*
|
||
|
* Note that a one-off listener will not change an existing listener,
|
||
|
* if any. On the other hand a normal listener will change existing
|
||
|
* one-off listener to become a normal listener.
|
||
|
*
|
||
|
* @param {string|!goog.events.EventId} type The listener event type.
|
||
|
* @param {!Function} listener This listener callback method.
|
||
|
* @param {boolean} callOnce Whether the listener is a one-off
|
||
|
* listener.
|
||
|
* @param {boolean=} opt_useCapture The capture mode of the listener.
|
||
|
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||
|
* listener.
|
||
|
* @return {!goog.events.ListenableKey} Unique key for the listener.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.add = function(
|
||
|
type, listener, callOnce, opt_useCapture, opt_listenerScope) {
|
||
|
var typeStr = type.toString();
|
||
|
var listenerArray = this.listeners[typeStr];
|
||
|
if (!listenerArray) {
|
||
|
listenerArray = this.listeners[typeStr] = [];
|
||
|
this.typeCount_++;
|
||
|
}
|
||
|
|
||
|
var listenerObj;
|
||
|
var index = goog.events.ListenerMap.findListenerIndex_(
|
||
|
listenerArray, listener, opt_useCapture, opt_listenerScope);
|
||
|
if (index > -1) {
|
||
|
listenerObj = listenerArray[index];
|
||
|
if (!callOnce) {
|
||
|
// Ensure that, if there is an existing callOnce listener, it is no
|
||
|
// longer a callOnce listener.
|
||
|
listenerObj.callOnce = false;
|
||
|
}
|
||
|
} else {
|
||
|
listenerObj = new goog.events.Listener(
|
||
|
listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
|
||
|
listenerObj.callOnce = callOnce;
|
||
|
listenerArray.push(listenerObj);
|
||
|
}
|
||
|
return listenerObj;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Removes a matching listener.
|
||
|
* @param {string|!goog.events.EventId} type The listener event type.
|
||
|
* @param {!Function} listener This listener callback method.
|
||
|
* @param {boolean=} opt_useCapture The capture mode of the listener.
|
||
|
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||
|
* listener.
|
||
|
* @return {boolean} Whether any listener was removed.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.remove = function(
|
||
|
type, listener, opt_useCapture, opt_listenerScope) {
|
||
|
var typeStr = type.toString();
|
||
|
if (!(typeStr in this.listeners)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var listenerArray = this.listeners[typeStr];
|
||
|
var index = goog.events.ListenerMap.findListenerIndex_(
|
||
|
listenerArray, listener, opt_useCapture, opt_listenerScope);
|
||
|
if (index > -1) {
|
||
|
var listenerObj = listenerArray[index];
|
||
|
listenerObj.markAsRemoved();
|
||
|
goog.array.removeAt(listenerArray, index);
|
||
|
if (listenerArray.length == 0) {
|
||
|
delete this.listeners[typeStr];
|
||
|
this.typeCount_--;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Removes the given listener object.
|
||
|
* @param {!goog.events.ListenableKey} listener The listener to remove.
|
||
|
* @return {boolean} Whether the listener is removed.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.removeByKey = function(listener) {
|
||
|
var type = listener.type;
|
||
|
if (!(type in this.listeners)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var removed = goog.array.remove(this.listeners[type], listener);
|
||
|
if (removed) {
|
||
|
listener.markAsRemoved();
|
||
|
if (this.listeners[type].length == 0) {
|
||
|
delete this.listeners[type];
|
||
|
this.typeCount_--;
|
||
|
}
|
||
|
}
|
||
|
return removed;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Removes all listeners from this map. If opt_type is provided, only
|
||
|
* listeners that match the given type are removed.
|
||
|
* @param {string|!goog.events.EventId=} opt_type Type of event to remove.
|
||
|
* @return {number} Number of listeners removed.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
|
||
|
var typeStr = opt_type && opt_type.toString();
|
||
|
var count = 0;
|
||
|
for (var type in this.listeners) {
|
||
|
if (!typeStr || type == typeStr) {
|
||
|
var listenerArray = this.listeners[type];
|
||
|
for (var i = 0; i < listenerArray.length; i++) {
|
||
|
++count;
|
||
|
listenerArray[i].markAsRemoved();
|
||
|
}
|
||
|
delete this.listeners[type];
|
||
|
this.typeCount_--;
|
||
|
}
|
||
|
}
|
||
|
return count;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Gets all listeners that match the given type and capture mode. The
|
||
|
* returned array is a copy (but the listener objects are not).
|
||
|
* @param {string|!goog.events.EventId} type The type of the listeners
|
||
|
* to retrieve.
|
||
|
* @param {boolean} capture The capture mode of the listeners to retrieve.
|
||
|
* @return {!Array<!goog.events.ListenableKey>} An array of matching
|
||
|
* listeners.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
|
||
|
var listenerArray = this.listeners[type.toString()];
|
||
|
var rv = [];
|
||
|
if (listenerArray) {
|
||
|
for (var i = 0; i < listenerArray.length; ++i) {
|
||
|
var listenerObj = listenerArray[i];
|
||
|
if (listenerObj.capture == capture) {
|
||
|
rv.push(listenerObj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return rv;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Gets the goog.events.ListenableKey for the event or null if no such
|
||
|
* listener is in use.
|
||
|
*
|
||
|
* @param {string|!goog.events.EventId} type The type of the listener
|
||
|
* to retrieve.
|
||
|
* @param {!Function} listener The listener function to get.
|
||
|
* @param {boolean} capture Whether the listener is a capturing listener.
|
||
|
* @param {Object=} opt_listenerScope Object in whose scope to call the
|
||
|
* listener.
|
||
|
* @return {goog.events.ListenableKey} the found listener or null if not found.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.getListener = function(
|
||
|
type, listener, capture, opt_listenerScope) {
|
||
|
var listenerArray = this.listeners[type.toString()];
|
||
|
var i = -1;
|
||
|
if (listenerArray) {
|
||
|
i = goog.events.ListenerMap.findListenerIndex_(
|
||
|
listenerArray, listener, capture, opt_listenerScope);
|
||
|
}
|
||
|
return i > -1 ? listenerArray[i] : null;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Whether there is a matching listener. If either the type or capture
|
||
|
* parameters are unspecified, the function will match on the
|
||
|
* remaining criteria.
|
||
|
*
|
||
|
* @param {string|!goog.events.EventId=} opt_type The type of the listener.
|
||
|
* @param {boolean=} opt_capture The capture mode of the listener.
|
||
|
* @return {boolean} Whether there is an active listener matching
|
||
|
* the requested type and/or capture phase.
|
||
|
*/
|
||
|
goog.events.ListenerMap.prototype.hasListener = function(
|
||
|
opt_type, opt_capture) {
|
||
|
var hasType = goog.isDef(opt_type);
|
||
|
var typeStr = hasType ? opt_type.toString() : '';
|
||
|
var hasCapture = goog.isDef(opt_capture);
|
||
|
|
||
|
return goog.object.some(this.listeners, function(listenerArray, type) {
|
||
|
for (var i = 0; i < listenerArray.length; ++i) {
|
||
|
if ((!hasType || listenerArray[i].type == typeStr) &&
|
||
|
(!hasCapture || listenerArray[i].capture == opt_capture)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
});
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Finds the index of a matching goog.events.Listener in the given
|
||
|
* listenerArray.
|
||
|
* @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
|
||
|
* @param {!Function} listener The listener function.
|
||
|
* @param {boolean=} opt_useCapture The capture flag for the listener.
|
||
|
* @param {Object=} opt_listenerScope The listener scope.
|
||
|
* @return {number} The index of the matching listener within the
|
||
|
* listenerArray.
|
||
|
* @private
|
||
|
*/
|
||
|
goog.events.ListenerMap.findListenerIndex_ = function(
|
||
|
listenerArray, listener, opt_useCapture, opt_listenerScope) {
|
||
|
for (var i = 0; i < listenerArray.length; ++i) {
|
||
|
var listenerObj = listenerArray[i];
|
||
|
if (!listenerObj.removed && listenerObj.listener == listener &&
|
||
|
listenerObj.capture == !!opt_useCapture &&
|
||
|
listenerObj.handler == opt_listenerScope) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
};
|