# lib/goog/events/listenable.js

    1// Copyright 2012 The Closure Library Authors. All Rights Reserved.2//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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**16 * @fileoverview An interface for a listenable JavaScript object.17 */1819goog.provide('goog.events.Listenable');20goog.provide('goog.events.ListenableKey');2122/** @suppress {extraRequire} */23goog.require('goog.events.EventId');24252627/**28 * A listenable interface. A listenable is an object with the ability29 * to dispatch/broadcast events to "event listeners" registered via30 * listen/listenOnce.31 *32 * The interface allows for an event propagation mechanism similar33 * to one offered by native browser event targets, such as34 * capture/bubble mechanism, stopping propagation, and preventing35 * default actions. Capture/bubble mechanism depends on the ancestor36 * tree constructed via {@code #getParentEventTarget}; this tree37 * must be directed acyclic graph. The meaning of default action(s)38 * in preventDefault is specific to a particular use case.39 *40 * Implementations that do not support capture/bubble or can not have41 * a parent listenable can simply not implement any ability to set the42 * parent listenable (and have {@code #getParentEventTarget} return43 * null).44 *45 * Implementation of this class can be used with or independently from46 * goog.events.47 *48 * Implementation must call {@code #addImplementation(implClass)}.49 *50 * @interface51 * @see goog.events52 * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html53 */54goog.events.Listenable = function() {};555657/**58 * An expando property to indicate that an object implements59 * goog.events.Listenable.60 *61 * See addImplementation/isImplementedBy.62 *63 * @type {string}64 * @const65 */66goog.events.Listenable.IMPLEMENTED_BY_PROP =67    'closure_listenable_' + ((Math.random() * 1e6) | 0);686970/**71 * Marks a given class (constructor) as an implementation of72 * Listenable, do that we can query that fact at runtime. The class73 * must have already implemented the interface.74 * @param {!Function} cls The class constructor. The corresponding75 *     class must have already implemented the interface.76 */77goog.events.Listenable.addImplementation = function(cls) {78  cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;79};808182/**83 * @param {Object} obj The object to check.84 * @return {boolean} Whether a given instance implements Listenable. The85 *     class/superclass of the instance must call addImplementation.86 */87goog.events.Listenable.isImplementedBy = function(obj) {88  return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);89};909192/**93 * Adds an event listener. A listener can only be added once to an94 * object and if it is added again the key for the listener is95 * returned. Note that if the existing listener is a one-off listener96 * (registered via listenOnce), it will no longer be a one-off97 * listener after a call to listen().98 *99 * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.100 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback101 *     method.102 * @param {boolean=} opt_useCapture Whether to fire in capture phase103 *     (defaults to false).104 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the105 *     listener.106 * @return {goog.events.ListenableKey} Unique key for the listener.107 * @template SCOPE,EVENTOBJ108 */109goog.events.Listenable.prototype.listen;110111112/**113 * Adds an event listener that is removed automatically after the114 * listener fired once.115 *116 * If an existing listener already exists, listenOnce will do117 * nothing. In particular, if the listener was previously registered118 * via listen(), listenOnce() will not turn the listener into a119 * one-off listener. Similarly, if there is already an existing120 * one-off listener, listenOnce does not modify the listeners (it is121 * still a once listener).122 *123 * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.124 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback125 *     method.126 * @param {boolean=} opt_useCapture Whether to fire in capture phase127 *     (defaults to false).128 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the129 *     listener.130 * @return {goog.events.ListenableKey} Unique key for the listener.131 * @template SCOPE,EVENTOBJ132 */133goog.events.Listenable.prototype.listenOnce;134135136/**137 * Removes an event listener which was added with listen() or listenOnce().138 *139 * @param {string|!goog.events.EventId.<EVENTOBJ>} type The event type id.140 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback141 *     method.142 * @param {boolean=} opt_useCapture Whether to fire in capture phase143 *     (defaults to false).144 * @param {SCOPE=} opt_listenerScope Object in whose scope to call145 *     the listener.146 * @return {boolean} Whether any listener was removed.147 * @template SCOPE,EVENTOBJ148 */149goog.events.Listenable.prototype.unlisten;150151152/**153 * Removes an event listener which was added with listen() by the key154 * returned by listen().155 *156 * @param {goog.events.ListenableKey} key The key returned by157 *     listen() or listenOnce().158 * @return {boolean} Whether any listener was removed.159 */160goog.events.Listenable.prototype.unlistenByKey;161162163/**164 * Dispatches an event (or event like object) and calls all listeners165 * listening for events of this type. The type of the event is decided by the166 * type property on the event object.167 *168 * If any of the listeners returns false OR calls preventDefault then this169 * function will return false.  If one of the capture listeners calls170 * stopPropagation, then the bubble listeners won't fire.171 *172 * @param {goog.events.EventLike} e Event object.173 * @return {boolean} If anyone called preventDefault on the event object (or174 *     if any of the listeners returns false) this will also return false.175 */176goog.events.Listenable.prototype.dispatchEvent;177178179/**180 * Removes all listeners from this listenable. If type is specified,181 * it will only remove listeners of the particular type. otherwise all182 * registered listeners will be removed.183 *184 * @param {string=} opt_type Type of event to remove, default is to185 *     remove all types.186 * @return {number} Number of listeners removed.187 */188goog.events.Listenable.prototype.removeAllListeners;189190191/**192 * Returns the parent of this event target to use for capture/bubble193 * mechanism.194 *195 * NOTE(user): The name reflects the original implementation of196 * custom event target ({@code goog.events.EventTarget}). We decided197 * that changing the name is not worth it.198 *199 * @return {goog.events.Listenable} The parent EventTarget or null if200 *     there is no parent.201 */202goog.events.Listenable.prototype.getParentEventTarget;203204205/**206 * Fires all registered listeners in this listenable for the given207 * type and capture mode, passing them the given eventObject. This208 * does not perform actual capture/bubble. Only implementors of the209 * interface should be using this.210 *211 * @param {string|!goog.events.EventId.<EVENTOBJ>} type The type of the212 *     listeners to fire.213 * @param {boolean} capture The capture mode of the listeners to fire.214 * @param {EVENTOBJ} eventObject The event object to fire.215 * @return {boolean} Whether all listeners succeeded without216 *     attempting to prevent default behavior. If any listener returns217 *     false or called goog.events.Event#preventDefault, this returns218 *     false.219 * @template EVENTOBJ220 */221goog.events.Listenable.prototype.fireListeners;222223224/**225 * Gets all listeners in this listenable for the given type and226 * capture mode.227 *228 * @param {string|!goog.events.EventId} type The type of the listeners to fire.229 * @param {boolean} capture The capture mode of the listeners to fire.230 * @return {!Array.<goog.events.ListenableKey>} An array of registered231 *     listeners.232 * @template EVENTOBJ233 */234goog.events.Listenable.prototype.getListeners;235236237/**238 * Gets the goog.events.ListenableKey for the event or null if no such239 * listener is in use.240 *241 * @param {string|!goog.events.EventId.<EVENTOBJ>} type The name of the event242 *     without the 'on' prefix.243 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The244 *     listener function to get.245 * @param {boolean} capture Whether the listener is a capturing listener.246 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the247 *     listener.248 * @return {goog.events.ListenableKey} the found listener or null if not found.249 * @template SCOPE,EVENTOBJ250 */251goog.events.Listenable.prototype.getListener;252253254/**255 * Whether there is any active listeners matching the specified256 * signature. If either the type or capture parameters are257 * unspecified, the function will match on the remaining criteria.258 *259 * @param {string|!goog.events.EventId.<EVENTOBJ>=} opt_type Event type.260 * @param {boolean=} opt_capture Whether to check for capture or bubble261 *     listeners.262 * @return {boolean} Whether there is any active listeners matching263 *     the requested type and/or capture phase.264 * @template EVENTOBJ265 */266goog.events.Listenable.prototype.hasListener;267268269270/**271 * An interface that describes a single registered listener.272 * @interface273 */274goog.events.ListenableKey = function() {};275276277/**278 * Counter used to create a unique key279 * @type {number}280 * @private281 */282goog.events.ListenableKey.counter_ = 0;283284285/**286 * Reserves a key to be used for ListenableKey#key field.287 * @return {number} A number to be used to fill ListenableKey#key288 *     field.289 */290goog.events.ListenableKey.reserveKey = function() {291  return ++goog.events.ListenableKey.counter_;292};293294295/**296 * The source event target.297 * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}298 */299goog.events.ListenableKey.prototype.src;300301302/**303 * The event type the listener is listening to.304 * @type {string}305 */306goog.events.ListenableKey.prototype.type;307308309/**310 * The listener function.311 * @type {function(?):?|{handleEvent:function(?):?}|null}312 */313goog.events.ListenableKey.prototype.listener;314315316/**317 * Whether the listener works on capture phase.318 * @type {boolean}319 */320goog.events.ListenableKey.prototype.capture;321322323/**324 * The 'this' object for the listener function's scope.325 * @type {Object}326 */327goog.events.ListenableKey.prototype.handler;328329330/**331 * A globally unique number to identify the key.332 * @type {number}333 */334goog.events.ListenableKey.prototype.key;