1 // Copyright 2013 The Closure Library Authors. All Rights Reserved.
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS-IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
16 * @fileoverview A map of listeners that provides utility functions to
17 * deal with listeners on an event target. Used by
18 * {@code goog.events.EventTarget}.
20 * WARNING: Do not use this class from outside goog.events package.
22 * @visibility {//closure/goog/bin/sizetests:__pkg__}
23 * @visibility {//closure/goog/events:__pkg__}
24 * @visibility {//closure/goog/labs/events:__pkg__}
27 goog.provide('goog.events.ListenerMap');
29 goog.require('goog.array');
30 goog.require('goog.events.Listener');
31 goog.require('goog.object');
36 * Creates a new listener map.
37 * @param {EventTarget|goog.events.Listenable} src The src object.
41 goog.events.ListenerMap = function(src) {
42 /** @type {EventTarget|goog.events.Listenable} */
46 * Maps of event type to an array of listeners.
47 * @type {Object.<string, !Array.<!goog.events.Listener>>}
52 * The count of types in this map that have registered listeners.
60 * @return {number} The count of event types in this map that actually
61 * have registered listeners.
63 goog.events.ListenerMap.prototype.getTypeCount = function() {
64 return this.typeCount_;
69 * @return {number} Total number of registered listeners.
71 goog.events.ListenerMap.prototype.getListenerCount = function() {
73 for (var type in this.listeners) {
74 count += this.listeners[type].length;
81 * Adds an event listener. A listener can only be added once to an
82 * object and if it is added again the key for the listener is
85 * Note that a one-off listener will not change an existing listener,
86 * if any. On the other hand a normal listener will change existing
87 * one-off listener to become a normal listener.
89 * @param {string|!goog.events.EventId} type The listener event type.
90 * @param {!Function} listener This listener callback method.
91 * @param {boolean} callOnce Whether the listener is a one-off
93 * @param {boolean=} opt_useCapture The capture mode of the listener.
94 * @param {Object=} opt_listenerScope Object in whose scope to call the
96 * @return {goog.events.ListenableKey} Unique key for the listener.
98 goog.events.ListenerMap.prototype.add = function(
99 type, listener, callOnce, opt_useCapture, opt_listenerScope) {
100 var typeStr = type.toString();
101 var listenerArray = this.listeners[typeStr];
102 if (!listenerArray) {
103 listenerArray = this.listeners[typeStr] = [];
108 var index = goog.events.ListenerMap.findListenerIndex_(
109 listenerArray, listener, opt_useCapture, opt_listenerScope);
111 listenerObj = listenerArray[index];
113 // Ensure that, if there is an existing callOnce listener, it is no
114 // longer a callOnce listener.
115 listenerObj.callOnce = false;
118 listenerObj = new goog.events.Listener(
119 listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
120 listenerObj.callOnce = callOnce;
121 listenerArray.push(listenerObj);
128 * Removes a matching listener.
129 * @param {string|!goog.events.EventId} type The listener event type.
130 * @param {!Function} listener This listener callback method.
131 * @param {boolean=} opt_useCapture The capture mode of the listener.
132 * @param {Object=} opt_listenerScope Object in whose scope to call the
134 * @return {boolean} Whether any listener was removed.
136 goog.events.ListenerMap.prototype.remove = function(
137 type, listener, opt_useCapture, opt_listenerScope) {
138 var typeStr = type.toString();
139 if (!(typeStr in this.listeners)) {
143 var listenerArray = this.listeners[typeStr];
144 var index = goog.events.ListenerMap.findListenerIndex_(
145 listenerArray, listener, opt_useCapture, opt_listenerScope);
147 var listenerObj = listenerArray[index];
148 listenerObj.markAsRemoved();
149 goog.array.removeAt(listenerArray, index);
150 if (listenerArray.length == 0) {
151 delete this.listeners[typeStr];
161 * Removes the given listener object.
162 * @param {goog.events.ListenableKey} listener The listener to remove.
163 * @return {boolean} Whether the listener is removed.
165 goog.events.ListenerMap.prototype.removeByKey = function(listener) {
166 var type = listener.type;
167 if (!(type in this.listeners)) {
171 var removed = goog.array.remove(this.listeners[type], listener);
173 listener.markAsRemoved();
174 if (this.listeners[type].length == 0) {
175 delete this.listeners[type];
184 * Removes all listeners from this map. If opt_type is provided, only
185 * listeners that match the given type are removed.
186 * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
187 * @return {number} Number of listeners removed.
189 goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
190 var typeStr = opt_type && opt_type.toString();
192 for (var type in this.listeners) {
193 if (!typeStr || type == typeStr) {
194 var listenerArray = this.listeners[type];
195 for (var i = 0; i < listenerArray.length; i++) {
197 listenerArray[i].markAsRemoved();
199 delete this.listeners[type];
208 * Gets all listeners that match the given type and capture mode. The
209 * returned array is a copy (but the listener objects are not).
210 * @param {string|!goog.events.EventId} type The type of the listeners
212 * @param {boolean} capture The capture mode of the listeners to retrieve.
213 * @return {!Array.<goog.events.ListenableKey>} An array of matching
216 goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
217 var listenerArray = this.listeners[type.toString()];
220 for (var i = 0; i < listenerArray.length; ++i) {
221 var listenerObj = listenerArray[i];
222 if (listenerObj.capture == capture) {
223 rv.push(listenerObj);
232 * Gets the goog.events.ListenableKey for the event or null if no such
233 * listener is in use.
235 * @param {string|!goog.events.EventId} type The type of the listener
237 * @param {!Function} listener The listener function to get.
238 * @param {boolean} capture Whether the listener is a capturing listener.
239 * @param {Object=} opt_listenerScope Object in whose scope to call the
241 * @return {goog.events.ListenableKey} the found listener or null if not found.
243 goog.events.ListenerMap.prototype.getListener = function(
244 type, listener, capture, opt_listenerScope) {
245 var listenerArray = this.listeners[type.toString()];
248 i = goog.events.ListenerMap.findListenerIndex_(
249 listenerArray, listener, capture, opt_listenerScope);
251 return i > -1 ? listenerArray[i] : null;
256 * Whether there is a matching listener. If either the type or capture
257 * parameters are unspecified, the function will match on the
258 * remaining criteria.
260 * @param {string|!goog.events.EventId=} opt_type The type of the listener.
261 * @param {boolean=} opt_capture The capture mode of the listener.
262 * @return {boolean} Whether there is an active listener matching
263 * the requested type and/or capture phase.
265 goog.events.ListenerMap.prototype.hasListener = function(
266 opt_type, opt_capture) {
267 var hasType = goog.isDef(opt_type);
268 var typeStr = hasType ? opt_type.toString() : '';
269 var hasCapture = goog.isDef(opt_capture);
271 return goog.object.some(
272 this.listeners, function(listenerArray, type) {
273 for (var i = 0; i < listenerArray.length; ++i) {
274 if ((!hasType || listenerArray[i].type == typeStr) &&
275 (!hasCapture || listenerArray[i].capture == opt_capture)) {
286 * Finds the index of a matching goog.events.Listener in the given
288 * @param {!Array.<!goog.events.Listener>} listenerArray Array of listener.
289 * @param {!Function} listener The listener function.
290 * @param {boolean=} opt_useCapture The capture flag for the listener.
291 * @param {Object=} opt_listenerScope The listener scope.
292 * @return {number} The index of the matching listener within the
296 goog.events.ListenerMap.findListenerIndex_ = function(
297 listenerArray, listener, opt_useCapture, opt_listenerScope) {
298 for (var i = 0; i < listenerArray.length; ++i) {
299 var listenerObj = listenerArray[i];
300 if (!listenerObj.removed &&
301 listenerObj.listener == listener &&
302 listenerObj.capture == !!opt_useCapture &&
303 listenerObj.handler == opt_listenerScope) {