eventtarget.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // Copyright 2005 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. * @fileoverview A disposable implementation of a custom
  16. * listenable/event target. See also: documentation for
  17. * {@code goog.events.Listenable}.
  18. *
  19. * @author arv@google.com (Erik Arvidsson) [Original implementation]
  20. * @see ../demos/eventtarget.html
  21. * @see goog.events.Listenable
  22. */
  23. goog.provide('goog.events.EventTarget');
  24. goog.require('goog.Disposable');
  25. goog.require('goog.asserts');
  26. goog.require('goog.events');
  27. goog.require('goog.events.Event');
  28. goog.require('goog.events.Listenable');
  29. goog.require('goog.events.ListenerMap');
  30. goog.require('goog.object');
  31. /**
  32. * An implementation of {@code goog.events.Listenable} with full W3C
  33. * EventTarget-like support (capture/bubble mechanism, stopping event
  34. * propagation, preventing default actions).
  35. *
  36. * You may subclass this class to turn your class into a Listenable.
  37. *
  38. * Unless propagation is stopped, an event dispatched by an
  39. * EventTarget will bubble to the parent returned by
  40. * {@code getParentEventTarget}. To set the parent, call
  41. * {@code setParentEventTarget}. Subclasses that don't support
  42. * changing the parent can override the setter to throw an error.
  43. *
  44. * Example usage:
  45. * <pre>
  46. * var source = new goog.events.EventTarget();
  47. * function handleEvent(e) {
  48. * alert('Type: ' + e.type + '; Target: ' + e.target);
  49. * }
  50. * source.listen('foo', handleEvent);
  51. * // Or: goog.events.listen(source, 'foo', handleEvent);
  52. * ...
  53. * source.dispatchEvent('foo'); // will call handleEvent
  54. * ...
  55. * source.unlisten('foo', handleEvent);
  56. * // Or: goog.events.unlisten(source, 'foo', handleEvent);
  57. * </pre>
  58. *
  59. * @constructor
  60. * @extends {goog.Disposable}
  61. * @implements {goog.events.Listenable}
  62. */
  63. goog.events.EventTarget = function() {
  64. goog.Disposable.call(this);
  65. /**
  66. * Maps of event type to an array of listeners.
  67. * @private {!goog.events.ListenerMap}
  68. */
  69. this.eventTargetListeners_ = new goog.events.ListenerMap(this);
  70. /**
  71. * The object to use for event.target. Useful when mixing in an
  72. * EventTarget to another object.
  73. * @private {!Object}
  74. */
  75. this.actualEventTarget_ = this;
  76. /**
  77. * Parent event target, used during event bubbling.
  78. *
  79. * TODO(chrishenry): Change this to goog.events.Listenable. This
  80. * currently breaks people who expect getParentEventTarget to return
  81. * goog.events.EventTarget.
  82. *
  83. * @private {goog.events.EventTarget}
  84. */
  85. this.parentEventTarget_ = null;
  86. };
  87. goog.inherits(goog.events.EventTarget, goog.Disposable);
  88. goog.events.Listenable.addImplementation(goog.events.EventTarget);
  89. /**
  90. * An artificial cap on the number of ancestors you can have. This is mainly
  91. * for loop detection.
  92. * @const {number}
  93. * @private
  94. */
  95. goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
  96. /**
  97. * Returns the parent of this event target to use for bubbling.
  98. *
  99. * @return {goog.events.EventTarget} The parent EventTarget or null if
  100. * there is no parent.
  101. * @override
  102. */
  103. goog.events.EventTarget.prototype.getParentEventTarget = function() {
  104. return this.parentEventTarget_;
  105. };
  106. /**
  107. * Sets the parent of this event target to use for capture/bubble
  108. * mechanism.
  109. * @param {goog.events.EventTarget} parent Parent listenable (null if none).
  110. */
  111. goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
  112. this.parentEventTarget_ = parent;
  113. };
  114. /**
  115. * Adds an event listener to the event target. The same handler can only be
  116. * added once per the type. Even if you add the same handler multiple times
  117. * using the same type then it will only be called once when the event is
  118. * dispatched.
  119. *
  120. * @param {string|!goog.events.EventId} type The type of the event to listen for
  121. * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
  122. * to handle the event. The handler can also be an object that implements
  123. * the handleEvent method which takes the event object as argument.
  124. * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
  125. * whether the listener is fired during the capture or bubble phase
  126. * of the event.
  127. * @param {Object=} opt_handlerScope Object in whose scope to call
  128. * the listener.
  129. * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
  130. * {@code goog.events.listen} if you are passing Object
  131. * (instead of Function) as handler.
  132. */
  133. goog.events.EventTarget.prototype.addEventListener = function(
  134. type, handler, opt_capture, opt_handlerScope) {
  135. goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
  136. };
  137. /**
  138. * Removes an event listener from the event target. The handler must be the
  139. * same object as the one added. If the handler has not been added then
  140. * nothing is done.
  141. *
  142. * @param {string} type The type of the event to listen for.
  143. * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
  144. * to handle the event. The handler can also be an object that implements
  145. * the handleEvent method which takes the event object as argument.
  146. * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
  147. * whether the listener is fired during the capture or bubble phase
  148. * of the event.
  149. * @param {Object=} opt_handlerScope Object in whose scope to call
  150. * the listener.
  151. * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
  152. * {@code goog.events.unlisten} if you are passing Object
  153. * (instead of Function) as handler.
  154. */
  155. goog.events.EventTarget.prototype.removeEventListener = function(
  156. type, handler, opt_capture, opt_handlerScope) {
  157. goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
  158. };
  159. /** @override */
  160. goog.events.EventTarget.prototype.dispatchEvent = function(e) {
  161. this.assertInitialized_();
  162. var ancestorsTree, ancestor = this.getParentEventTarget();
  163. if (ancestor) {
  164. ancestorsTree = [];
  165. var ancestorCount = 1;
  166. for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
  167. ancestorsTree.push(ancestor);
  168. goog.asserts.assert(
  169. (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
  170. 'infinite loop');
  171. }
  172. }
  173. return goog.events.EventTarget.dispatchEventInternal_(
  174. this.actualEventTarget_, e, ancestorsTree);
  175. };
  176. /**
  177. * Removes listeners from this object. Classes that extend EventTarget may
  178. * need to override this method in order to remove references to DOM Elements
  179. * and additional listeners.
  180. * @override
  181. */
  182. goog.events.EventTarget.prototype.disposeInternal = function() {
  183. goog.events.EventTarget.superClass_.disposeInternal.call(this);
  184. this.removeAllListeners();
  185. this.parentEventTarget_ = null;
  186. };
  187. /** @override */
  188. goog.events.EventTarget.prototype.listen = function(
  189. type, listener, opt_useCapture, opt_listenerScope) {
  190. this.assertInitialized_();
  191. return this.eventTargetListeners_.add(
  192. String(type), listener, false /* callOnce */, opt_useCapture,
  193. opt_listenerScope);
  194. };
  195. /** @override */
  196. goog.events.EventTarget.prototype.listenOnce = function(
  197. type, listener, opt_useCapture, opt_listenerScope) {
  198. return this.eventTargetListeners_.add(
  199. String(type), listener, true /* callOnce */, opt_useCapture,
  200. opt_listenerScope);
  201. };
  202. /** @override */
  203. goog.events.EventTarget.prototype.unlisten = function(
  204. type, listener, opt_useCapture, opt_listenerScope) {
  205. return this.eventTargetListeners_.remove(
  206. String(type), listener, opt_useCapture, opt_listenerScope);
  207. };
  208. /** @override */
  209. goog.events.EventTarget.prototype.unlistenByKey = function(key) {
  210. return this.eventTargetListeners_.removeByKey(key);
  211. };
  212. /** @override */
  213. goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
  214. // TODO(chrishenry): Previously, removeAllListeners can be called on
  215. // uninitialized EventTarget, so we preserve that behavior. We
  216. // should remove this when usages that rely on that fact are purged.
  217. if (!this.eventTargetListeners_) {
  218. return 0;
  219. }
  220. return this.eventTargetListeners_.removeAll(opt_type);
  221. };
  222. /** @override */
  223. goog.events.EventTarget.prototype.fireListeners = function(
  224. type, capture, eventObject) {
  225. // TODO(chrishenry): Original code avoids array creation when there
  226. // is no listener, so we do the same. If this optimization turns
  227. // out to be not required, we can replace this with
  228. // getListeners(type, capture) instead, which is simpler.
  229. var listenerArray = this.eventTargetListeners_.listeners[String(type)];
  230. if (!listenerArray) {
  231. return true;
  232. }
  233. listenerArray = listenerArray.concat();
  234. var rv = true;
  235. for (var i = 0; i < listenerArray.length; ++i) {
  236. var listener = listenerArray[i];
  237. // We might not have a listener if the listener was removed.
  238. if (listener && !listener.removed && listener.capture == capture) {
  239. var listenerFn = listener.listener;
  240. var listenerHandler = listener.handler || listener.src;
  241. if (listener.callOnce) {
  242. this.unlistenByKey(listener);
  243. }
  244. rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
  245. }
  246. }
  247. return rv && eventObject.returnValue_ != false;
  248. };
  249. /** @override */
  250. goog.events.EventTarget.prototype.getListeners = function(type, capture) {
  251. return this.eventTargetListeners_.getListeners(String(type), capture);
  252. };
  253. /** @override */
  254. goog.events.EventTarget.prototype.getListener = function(
  255. type, listener, capture, opt_listenerScope) {
  256. return this.eventTargetListeners_.getListener(
  257. String(type), listener, capture, opt_listenerScope);
  258. };
  259. /** @override */
  260. goog.events.EventTarget.prototype.hasListener = function(
  261. opt_type, opt_capture) {
  262. var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
  263. return this.eventTargetListeners_.hasListener(id, opt_capture);
  264. };
  265. /**
  266. * Sets the target to be used for {@code event.target} when firing
  267. * event. Mainly used for testing. For example, see
  268. * {@code goog.testing.events.mixinListenable}.
  269. * @param {!Object} target The target.
  270. */
  271. goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
  272. this.actualEventTarget_ = target;
  273. };
  274. /**
  275. * Asserts that the event target instance is initialized properly.
  276. * @private
  277. */
  278. goog.events.EventTarget.prototype.assertInitialized_ = function() {
  279. goog.asserts.assert(
  280. this.eventTargetListeners_,
  281. 'Event target is not initialized. Did you call the superclass ' +
  282. '(goog.events.EventTarget) constructor?');
  283. };
  284. /**
  285. * Dispatches the given event on the ancestorsTree.
  286. *
  287. * @param {!Object} target The target to dispatch on.
  288. * @param {goog.events.Event|Object|string} e The event object.
  289. * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
  290. * tree of the target, in reverse order from the closest ancestor
  291. * to the root event target. May be null if the target has no ancestor.
  292. * @return {boolean} If anyone called preventDefault on the event object (or
  293. * if any of the listeners returns false) this will also return false.
  294. * @private
  295. */
  296. goog.events.EventTarget.dispatchEventInternal_ = function(
  297. target, e, opt_ancestorsTree) {
  298. var type = e.type || /** @type {string} */ (e);
  299. // If accepting a string or object, create a custom event object so that
  300. // preventDefault and stopPropagation work with the event.
  301. if (goog.isString(e)) {
  302. e = new goog.events.Event(e, target);
  303. } else if (!(e instanceof goog.events.Event)) {
  304. var oldEvent = e;
  305. e = new goog.events.Event(type, target);
  306. goog.object.extend(e, oldEvent);
  307. } else {
  308. e.target = e.target || target;
  309. }
  310. var rv = true, currentTarget;
  311. // Executes all capture listeners on the ancestors, if any.
  312. if (opt_ancestorsTree) {
  313. for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
  314. i--) {
  315. currentTarget = e.currentTarget = opt_ancestorsTree[i];
  316. rv = currentTarget.fireListeners(type, true, e) && rv;
  317. }
  318. }
  319. // Executes capture and bubble listeners on the target.
  320. if (!e.propagationStopped_) {
  321. currentTarget = /** @type {?} */ (e.currentTarget = target);
  322. rv = currentTarget.fireListeners(type, true, e) && rv;
  323. if (!e.propagationStopped_) {
  324. rv = currentTarget.fireListeners(type, false, e) && rv;
  325. }
  326. }
  327. // Executes all bubble listeners on the ancestors, if any.
  328. if (opt_ancestorsTree) {
  329. for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
  330. currentTarget = e.currentTarget = opt_ancestorsTree[i];
  331. rv = currentTarget.fireListeners(type, false, e) && rv;
  332. }
  333. }
  334. return rv;
  335. };