| 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 at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 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. |
| 14 | |
| 15 | /** |
| 16 | * @fileoverview An interface for a listenable JavaScript object. |
| 17 | */ |
| 18 | |
| 19 | goog.provide('goog.events.Listenable'); |
| 20 | goog.provide('goog.events.ListenableKey'); |
| 21 | |
| 22 | /** @suppress {extraRequire} */ |
| 23 | goog.require('goog.events.EventId'); |
| 24 | |
| 25 | |
| 26 | |
| 27 | /** |
| 28 | * A listenable interface. A listenable is an object with the ability |
| 29 | * to dispatch/broadcast events to "event listeners" registered via |
| 30 | * listen/listenOnce. |
| 31 | * |
| 32 | * The interface allows for an event propagation mechanism similar |
| 33 | * to one offered by native browser event targets, such as |
| 34 | * capture/bubble mechanism, stopping propagation, and preventing |
| 35 | * default actions. Capture/bubble mechanism depends on the ancestor |
| 36 | * tree constructed via {@code #getParentEventTarget}; this tree |
| 37 | * 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 have |
| 41 | * a parent listenable can simply not implement any ability to set the |
| 42 | * parent listenable (and have {@code #getParentEventTarget} return |
| 43 | * null). |
| 44 | * |
| 45 | * Implementation of this class can be used with or independently from |
| 46 | * goog.events. |
| 47 | * |
| 48 | * Implementation must call {@code #addImplementation(implClass)}. |
| 49 | * |
| 50 | * @interface |
| 51 | * @see goog.events |
| 52 | * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html |
| 53 | */ |
| 54 | goog.events.Listenable = function() {}; |
| 55 | |
| 56 | |
| 57 | /** |
| 58 | * An expando property to indicate that an object implements |
| 59 | * goog.events.Listenable. |
| 60 | * |
| 61 | * See addImplementation/isImplementedBy. |
| 62 | * |
| 63 | * @type {string} |
| 64 | * @const |
| 65 | */ |
| 66 | goog.events.Listenable.IMPLEMENTED_BY_PROP = |
| 67 | 'closure_listenable_' + ((Math.random() * 1e6) | 0); |
| 68 | |
| 69 | |
| 70 | /** |
| 71 | * Marks a given class (constructor) as an implementation of |
| 72 | * Listenable, do that we can query that fact at runtime. The class |
| 73 | * must have already implemented the interface. |
| 74 | * @param {!Function} cls The class constructor. The corresponding |
| 75 | * class must have already implemented the interface. |
| 76 | */ |
| 77 | goog.events.Listenable.addImplementation = function(cls) { |
| 78 | cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true; |
| 79 | }; |
| 80 | |
| 81 | |
| 82 | /** |
| 83 | * @param {Object} obj The object to check. |
| 84 | * @return {boolean} Whether a given instance implements Listenable. The |
| 85 | * class/superclass of the instance must call addImplementation. |
| 86 | */ |
| 87 | goog.events.Listenable.isImplementedBy = function(obj) { |
| 88 | return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]); |
| 89 | }; |
| 90 | |
| 91 | |
| 92 | /** |
| 93 | * Adds an event listener. A listener can only be added once to an |
| 94 | * object and if it is added again the key for the listener is |
| 95 | * returned. Note that if the existing listener is a one-off listener |
| 96 | * (registered via listenOnce), it will no longer be a one-off |
| 97 | * 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 Callback |
| 101 | * method. |
| 102 | * @param {boolean=} opt_useCapture Whether to fire in capture phase |
| 103 | * (defaults to false). |
| 104 | * @param {SCOPE=} opt_listenerScope Object in whose scope to call the |
| 105 | * listener. |
| 106 | * @return {goog.events.ListenableKey} Unique key for the listener. |
| 107 | * @template SCOPE,EVENTOBJ |
| 108 | */ |
| 109 | goog.events.Listenable.prototype.listen; |
| 110 | |
| 111 | |
| 112 | /** |
| 113 | * Adds an event listener that is removed automatically after the |
| 114 | * listener fired once. |
| 115 | * |
| 116 | * If an existing listener already exists, listenOnce will do |
| 117 | * nothing. In particular, if the listener was previously registered |
| 118 | * via listen(), listenOnce() will not turn the listener into a |
| 119 | * one-off listener. Similarly, if there is already an existing |
| 120 | * one-off listener, listenOnce does not modify the listeners (it is |
| 121 | * 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 Callback |
| 125 | * method. |
| 126 | * @param {boolean=} opt_useCapture Whether to fire in capture phase |
| 127 | * (defaults to false). |
| 128 | * @param {SCOPE=} opt_listenerScope Object in whose scope to call the |
| 129 | * listener. |
| 130 | * @return {goog.events.ListenableKey} Unique key for the listener. |
| 131 | * @template SCOPE,EVENTOBJ |
| 132 | */ |
| 133 | goog.events.Listenable.prototype.listenOnce; |
| 134 | |
| 135 | |
| 136 | /** |
| 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 Callback |
| 141 | * method. |
| 142 | * @param {boolean=} opt_useCapture Whether to fire in capture phase |
| 143 | * (defaults to false). |
| 144 | * @param {SCOPE=} opt_listenerScope Object in whose scope to call |
| 145 | * the listener. |
| 146 | * @return {boolean} Whether any listener was removed. |
| 147 | * @template SCOPE,EVENTOBJ |
| 148 | */ |
| 149 | goog.events.Listenable.prototype.unlisten; |
| 150 | |
| 151 | |
| 152 | /** |
| 153 | * Removes an event listener which was added with listen() by the key |
| 154 | * returned by listen(). |
| 155 | * |
| 156 | * @param {goog.events.ListenableKey} key The key returned by |
| 157 | * listen() or listenOnce(). |
| 158 | * @return {boolean} Whether any listener was removed. |
| 159 | */ |
| 160 | goog.events.Listenable.prototype.unlistenByKey; |
| 161 | |
| 162 | |
| 163 | /** |
| 164 | * Dispatches an event (or event like object) and calls all listeners |
| 165 | * listening for events of this type. The type of the event is decided by the |
| 166 | * type property on the event object. |
| 167 | * |
| 168 | * If any of the listeners returns false OR calls preventDefault then this |
| 169 | * function will return false. If one of the capture listeners calls |
| 170 | * 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 (or |
| 174 | * if any of the listeners returns false) this will also return false. |
| 175 | */ |
| 176 | goog.events.Listenable.prototype.dispatchEvent; |
| 177 | |
| 178 | |
| 179 | /** |
| 180 | * Removes all listeners from this listenable. If type is specified, |
| 181 | * it will only remove listeners of the particular type. otherwise all |
| 182 | * registered listeners will be removed. |
| 183 | * |
| 184 | * @param {string=} opt_type Type of event to remove, default is to |
| 185 | * remove all types. |
| 186 | * @return {number} Number of listeners removed. |
| 187 | */ |
| 188 | goog.events.Listenable.prototype.removeAllListeners; |
| 189 | |
| 190 | |
| 191 | /** |
| 192 | * Returns the parent of this event target to use for capture/bubble |
| 193 | * mechanism. |
| 194 | * |
| 195 | * NOTE(user): The name reflects the original implementation of |
| 196 | * custom event target ({@code goog.events.EventTarget}). We decided |
| 197 | * that changing the name is not worth it. |
| 198 | * |
| 199 | * @return {goog.events.Listenable} The parent EventTarget or null if |
| 200 | * there is no parent. |
| 201 | */ |
| 202 | goog.events.Listenable.prototype.getParentEventTarget; |
| 203 | |
| 204 | |
| 205 | /** |
| 206 | * Fires all registered listeners in this listenable for the given |
| 207 | * type and capture mode, passing them the given eventObject. This |
| 208 | * does not perform actual capture/bubble. Only implementors of the |
| 209 | * interface should be using this. |
| 210 | * |
| 211 | * @param {string|!goog.events.EventId.<EVENTOBJ>} type The type of the |
| 212 | * 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 without |
| 216 | * attempting to prevent default behavior. If any listener returns |
| 217 | * false or called goog.events.Event#preventDefault, this returns |
| 218 | * false. |
| 219 | * @template EVENTOBJ |
| 220 | */ |
| 221 | goog.events.Listenable.prototype.fireListeners; |
| 222 | |
| 223 | |
| 224 | /** |
| 225 | * Gets all listeners in this listenable for the given type and |
| 226 | * 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 registered |
| 231 | * listeners. |
| 232 | * @template EVENTOBJ |
| 233 | */ |
| 234 | goog.events.Listenable.prototype.getListeners; |
| 235 | |
| 236 | |
| 237 | /** |
| 238 | * Gets the goog.events.ListenableKey for the event or null if no such |
| 239 | * listener is in use. |
| 240 | * |
| 241 | * @param {string|!goog.events.EventId.<EVENTOBJ>} type The name of the event |
| 242 | * without the 'on' prefix. |
| 243 | * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The |
| 244 | * 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 the |
| 247 | * listener. |
| 248 | * @return {goog.events.ListenableKey} the found listener or null if not found. |
| 249 | * @template SCOPE,EVENTOBJ |
| 250 | */ |
| 251 | goog.events.Listenable.prototype.getListener; |
| 252 | |
| 253 | |
| 254 | /** |
| 255 | * Whether there is any active listeners matching the specified |
| 256 | * signature. If either the type or capture parameters are |
| 257 | * 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 bubble |
| 261 | * listeners. |
| 262 | * @return {boolean} Whether there is any active listeners matching |
| 263 | * the requested type and/or capture phase. |
| 264 | * @template EVENTOBJ |
| 265 | */ |
| 266 | goog.events.Listenable.prototype.hasListener; |
| 267 | |
| 268 | |
| 269 | |
| 270 | /** |
| 271 | * An interface that describes a single registered listener. |
| 272 | * @interface |
| 273 | */ |
| 274 | goog.events.ListenableKey = function() {}; |
| 275 | |
| 276 | |
| 277 | /** |
| 278 | * Counter used to create a unique key |
| 279 | * @type {number} |
| 280 | * @private |
| 281 | */ |
| 282 | goog.events.ListenableKey.counter_ = 0; |
| 283 | |
| 284 | |
| 285 | /** |
| 286 | * Reserves a key to be used for ListenableKey#key field. |
| 287 | * @return {number} A number to be used to fill ListenableKey#key |
| 288 | * field. |
| 289 | */ |
| 290 | goog.events.ListenableKey.reserveKey = function() { |
| 291 | return ++goog.events.ListenableKey.counter_; |
| 292 | }; |
| 293 | |
| 294 | |
| 295 | /** |
| 296 | * The source event target. |
| 297 | * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)} |
| 298 | */ |
| 299 | goog.events.ListenableKey.prototype.src; |
| 300 | |
| 301 | |
| 302 | /** |
| 303 | * The event type the listener is listening to. |
| 304 | * @type {string} |
| 305 | */ |
| 306 | goog.events.ListenableKey.prototype.type; |
| 307 | |
| 308 | |
| 309 | /** |
| 310 | * The listener function. |
| 311 | * @type {function(?):?|{handleEvent:function(?):?}|null} |
| 312 | */ |
| 313 | goog.events.ListenableKey.prototype.listener; |
| 314 | |
| 315 | |
| 316 | /** |
| 317 | * Whether the listener works on capture phase. |
| 318 | * @type {boolean} |
| 319 | */ |
| 320 | goog.events.ListenableKey.prototype.capture; |
| 321 | |
| 322 | |
| 323 | /** |
| 324 | * The 'this' object for the listener function's scope. |
| 325 | * @type {Object} |
| 326 | */ |
| 327 | goog.events.ListenableKey.prototype.handler; |
| 328 | |
| 329 | |
| 330 | /** |
| 331 | * A globally unique number to identify the key. |
| 332 | * @type {number} |
| 333 | */ |
| 334 | goog.events.ListenableKey.prototype.key; |