nondisposableeventtarget.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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 An implementation of {@link goog.events.Listenable} that does
  16. * not need to be disposed.
  17. */
  18. goog.provide('goog.labs.events.NonDisposableEventTarget');
  19. goog.require('goog.array');
  20. goog.require('goog.asserts');
  21. goog.require('goog.events.Event');
  22. goog.require('goog.events.Listenable');
  23. goog.require('goog.events.ListenerMap');
  24. goog.require('goog.object');
  25. /**
  26. * An implementation of {@code goog.events.Listenable} with full W3C
  27. * EventTarget-like support (capture/bubble mechanism, stopping event
  28. * propagation, preventing default actions).
  29. *
  30. * You may subclass this class to turn your class into a Listenable.
  31. *
  32. * Unlike {@link goog.events.EventTarget}, this class does not implement
  33. * {@link goog.disposable.IDisposable}. Instances of this class that have had
  34. * It is not necessary to call {@link goog.dispose}
  35. * or {@link #removeAllListeners} in order for an instance of this class
  36. * to be garbage collected.
  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.labs.events.NonDisposableEventTarget();
  47. * function handleEvent(e) {
  48. * alert('Type: ' + e.type + '; Target: ' + e.target);
  49. * }
  50. * source.listen('foo', handleEvent);
  51. * source.dispatchEvent('foo'); // will call handleEvent
  52. * </pre>
  53. *
  54. * TODO(chrishenry|johnlenz): Consider a more modern, less viral
  55. * (not based on inheritance) replacement of goog.Disposable, which will allow
  56. * goog.events.EventTarget to not be disposable.
  57. *
  58. * @constructor
  59. * @implements {goog.events.Listenable}
  60. * @final
  61. */
  62. goog.labs.events.NonDisposableEventTarget = function() {
  63. /**
  64. * Maps of event type to an array of listeners.
  65. * @private {!goog.events.ListenerMap}
  66. */
  67. this.eventTargetListeners_ = new goog.events.ListenerMap(this);
  68. };
  69. goog.events.Listenable.addImplementation(
  70. goog.labs.events.NonDisposableEventTarget);
  71. /**
  72. * An artificial cap on the number of ancestors you can have. This is mainly
  73. * for loop detection.
  74. * @const {number}
  75. * @private
  76. */
  77. goog.labs.events.NonDisposableEventTarget.MAX_ANCESTORS_ = 1000;
  78. /**
  79. * Parent event target, used during event bubbling.
  80. * @private {goog.events.Listenable}
  81. */
  82. goog.labs.events.NonDisposableEventTarget.prototype.parentEventTarget_ = null;
  83. /** @override */
  84. goog.labs.events.NonDisposableEventTarget.prototype.getParentEventTarget =
  85. function() {
  86. return this.parentEventTarget_;
  87. };
  88. /**
  89. * Sets the parent of this event target to use for capture/bubble
  90. * mechanism.
  91. * @param {goog.events.Listenable} parent Parent listenable (null if none).
  92. */
  93. goog.labs.events.NonDisposableEventTarget.prototype.setParentEventTarget =
  94. function(parent) {
  95. this.parentEventTarget_ = parent;
  96. };
  97. /** @override */
  98. goog.labs.events.NonDisposableEventTarget.prototype.dispatchEvent = function(
  99. e) {
  100. this.assertInitialized_();
  101. var ancestorsTree, ancestor = this.getParentEventTarget();
  102. if (ancestor) {
  103. ancestorsTree = [];
  104. var ancestorCount = 1;
  105. for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
  106. ancestorsTree.push(ancestor);
  107. goog.asserts.assert(
  108. (++ancestorCount <
  109. goog.labs.events.NonDisposableEventTarget.MAX_ANCESTORS_),
  110. 'infinite loop');
  111. }
  112. }
  113. return goog.labs.events.NonDisposableEventTarget.dispatchEventInternal_(
  114. this, e, ancestorsTree);
  115. };
  116. /** @override */
  117. goog.labs.events.NonDisposableEventTarget.prototype.listen = function(
  118. type, listener, opt_useCapture, opt_listenerScope) {
  119. this.assertInitialized_();
  120. return this.eventTargetListeners_.add(
  121. String(type), listener, false /* callOnce */, opt_useCapture,
  122. opt_listenerScope);
  123. };
  124. /** @override */
  125. goog.labs.events.NonDisposableEventTarget.prototype.listenOnce = function(
  126. type, listener, opt_useCapture, opt_listenerScope) {
  127. return this.eventTargetListeners_.add(
  128. String(type), listener, true /* callOnce */, opt_useCapture,
  129. opt_listenerScope);
  130. };
  131. /** @override */
  132. goog.labs.events.NonDisposableEventTarget.prototype.unlisten = function(
  133. type, listener, opt_useCapture, opt_listenerScope) {
  134. return this.eventTargetListeners_.remove(
  135. String(type), listener, opt_useCapture, opt_listenerScope);
  136. };
  137. /** @override */
  138. goog.labs.events.NonDisposableEventTarget.prototype.unlistenByKey = function(
  139. key) {
  140. return this.eventTargetListeners_.removeByKey(key);
  141. };
  142. /** @override */
  143. goog.labs.events.NonDisposableEventTarget.prototype.removeAllListeners =
  144. function(opt_type) {
  145. return this.eventTargetListeners_.removeAll(opt_type);
  146. };
  147. /** @override */
  148. goog.labs.events.NonDisposableEventTarget.prototype.fireListeners = function(
  149. type, capture, eventObject) {
  150. // TODO(chrishenry): Original code avoids array creation when there
  151. // is no listener, so we do the same. If this optimization turns
  152. // out to be not required, we can replace this with
  153. // getListeners(type, capture) instead, which is simpler.
  154. var listenerArray = this.eventTargetListeners_.listeners[String(type)];
  155. if (!listenerArray) {
  156. return true;
  157. }
  158. listenerArray = goog.array.clone(listenerArray);
  159. var rv = true;
  160. for (var i = 0; i < listenerArray.length; ++i) {
  161. var listener = listenerArray[i];
  162. // We might not have a listener if the listener was removed.
  163. if (listener && !listener.removed && listener.capture == capture) {
  164. var listenerFn = listener.listener;
  165. var listenerHandler = listener.handler || listener.src;
  166. if (listener.callOnce) {
  167. this.unlistenByKey(listener);
  168. }
  169. rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
  170. }
  171. }
  172. return rv && eventObject.returnValue_ != false;
  173. };
  174. /** @override */
  175. goog.labs.events.NonDisposableEventTarget.prototype.getListeners = function(
  176. type, capture) {
  177. return this.eventTargetListeners_.getListeners(String(type), capture);
  178. };
  179. /** @override */
  180. goog.labs.events.NonDisposableEventTarget.prototype.getListener = function(
  181. type, listener, capture, opt_listenerScope) {
  182. return this.eventTargetListeners_.getListener(
  183. String(type), listener, capture, opt_listenerScope);
  184. };
  185. /** @override */
  186. goog.labs.events.NonDisposableEventTarget.prototype.hasListener = function(
  187. opt_type, opt_capture) {
  188. var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
  189. return this.eventTargetListeners_.hasListener(id, opt_capture);
  190. };
  191. /**
  192. * Asserts that the event target instance is initialized properly.
  193. * @private
  194. */
  195. goog.labs.events.NonDisposableEventTarget.prototype.assertInitialized_ =
  196. function() {
  197. goog.asserts.assert(
  198. this.eventTargetListeners_,
  199. 'Event target is not initialized. Did you call the superclass ' +
  200. '(goog.labs.events.NonDisposableEventTarget) constructor?');
  201. };
  202. /**
  203. * Dispatches the given event on the ancestorsTree.
  204. *
  205. * TODO(chrishenry): Look for a way to reuse this logic in
  206. * goog.events, if possible.
  207. *
  208. * @param {!Object} target The target to dispatch on.
  209. * @param {goog.events.Event|Object|string} e The event object.
  210. * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
  211. * tree of the target, in reverse order from the closest ancestor
  212. * to the root event target. May be null if the target has no ancestor.
  213. * @return {boolean} If anyone called preventDefault on the event object (or
  214. * if any of the listeners returns false) this will also return false.
  215. * @private
  216. */
  217. goog.labs.events.NonDisposableEventTarget.dispatchEventInternal_ = function(
  218. target, e, opt_ancestorsTree) {
  219. var type = e.type || /** @type {string} */ (e);
  220. // If accepting a string or object, create a custom event object so that
  221. // preventDefault and stopPropagation work with the event.
  222. if (goog.isString(e)) {
  223. e = new goog.events.Event(e, target);
  224. } else if (!(e instanceof goog.events.Event)) {
  225. var oldEvent = e;
  226. e = new goog.events.Event(type, target);
  227. goog.object.extend(e, oldEvent);
  228. } else {
  229. e.target = e.target || target;
  230. }
  231. var rv = true, currentTarget;
  232. // Executes all capture listeners on the ancestors, if any.
  233. if (opt_ancestorsTree) {
  234. for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
  235. i--) {
  236. currentTarget = e.currentTarget = opt_ancestorsTree[i];
  237. rv = currentTarget.fireListeners(type, true, e) && rv;
  238. }
  239. }
  240. // Executes capture and bubble listeners on the target.
  241. if (!e.propagationStopped_) {
  242. currentTarget = e.currentTarget = target;
  243. rv = currentTarget.fireListeners(type, true, e) && rv;
  244. if (!e.propagationStopped_) {
  245. rv = currentTarget.fireListeners(type, false, e) && rv;
  246. }
  247. }
  248. // Executes all bubble listeners on the ancestors, if any.
  249. if (opt_ancestorsTree) {
  250. for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
  251. currentTarget = e.currentTarget = opt_ancestorsTree[i];
  252. rv = currentTarget.fireListeners(type, false, e) && rv;
  253. }
  254. }
  255. return rv;
  256. };