eventhandler.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 Class to create objects which want to handle multiple events
  16. * and have their listeners easily cleaned up via a dispose method.
  17. *
  18. * Example:
  19. * <pre>
  20. * function Something() {
  21. * Something.base(this);
  22. *
  23. * ... set up object ...
  24. *
  25. * // Add event listeners
  26. * this.listen(this.starEl, goog.events.EventType.CLICK, this.handleStar);
  27. * this.listen(this.headerEl, goog.events.EventType.CLICK, this.expand);
  28. * this.listen(this.collapseEl, goog.events.EventType.CLICK, this.collapse);
  29. * this.listen(this.infoEl, goog.events.EventType.MOUSEOVER, this.showHover);
  30. * this.listen(this.infoEl, goog.events.EventType.MOUSEOUT, this.hideHover);
  31. * }
  32. * goog.inherits(Something, goog.events.EventHandler);
  33. *
  34. * Something.prototype.disposeInternal = function() {
  35. * Something.base(this, 'disposeInternal');
  36. * goog.dom.removeNode(this.container);
  37. * };
  38. *
  39. *
  40. * // Then elsewhere:
  41. *
  42. * var activeSomething = null;
  43. * function openSomething() {
  44. * activeSomething = new Something();
  45. * }
  46. *
  47. * function closeSomething() {
  48. * if (activeSomething) {
  49. * activeSomething.dispose(); // Remove event listeners
  50. * activeSomething = null;
  51. * }
  52. * }
  53. * </pre>
  54. *
  55. */
  56. goog.provide('goog.events.EventHandler');
  57. goog.require('goog.Disposable');
  58. goog.require('goog.events');
  59. goog.require('goog.object');
  60. goog.forwardDeclare('goog.events.EventWrapper');
  61. /**
  62. * Super class for objects that want to easily manage a number of event
  63. * listeners. It allows a short cut to listen and also provides a quick way
  64. * to remove all events listeners belonging to this object.
  65. * @param {SCOPE=} opt_scope Object in whose scope to call the listeners.
  66. * @constructor
  67. * @extends {goog.Disposable}
  68. * @template SCOPE
  69. */
  70. goog.events.EventHandler = function(opt_scope) {
  71. goog.Disposable.call(this);
  72. // TODO(mknichel): Rename this to this.scope_ and fix the classes in google3
  73. // that access this private variable. :(
  74. this.handler_ = opt_scope;
  75. /**
  76. * Keys for events that are being listened to.
  77. * @type {!Object<!goog.events.Key>}
  78. * @private
  79. */
  80. this.keys_ = {};
  81. };
  82. goog.inherits(goog.events.EventHandler, goog.Disposable);
  83. /**
  84. * Utility array used to unify the cases of listening for an array of types
  85. * and listening for a single event, without using recursion or allocating
  86. * an array each time.
  87. * @type {!Array<string>}
  88. * @const
  89. * @private
  90. */
  91. goog.events.EventHandler.typeArray_ = [];
  92. /**
  93. * Listen to an event on a Listenable. If the function is omitted then the
  94. * EventHandler's handleEvent method will be used.
  95. * @param {goog.events.ListenableType} src Event source.
  96. * @param {string|Array<string>|
  97. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  98. * type Event type to listen for or array of event types.
  99. * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
  100. * opt_fn Optional callback function to be used as the listener or an object
  101. * with handleEvent function.
  102. * @param {(boolean|!AddEventListenerOptions)=} opt_options
  103. * @return {THIS} This object, allowing for chaining of calls.
  104. * @this {THIS}
  105. * @template EVENTOBJ, THIS
  106. */
  107. goog.events.EventHandler.prototype.listen = function(
  108. src, type, opt_fn, opt_options) {
  109. var self = /** @type {!goog.events.EventHandler} */ (this);
  110. return self.listen_(src, type, opt_fn, opt_options);
  111. };
  112. /**
  113. * Listen to an event on a Listenable. If the function is omitted then the
  114. * EventHandler's handleEvent method will be used.
  115. * @param {goog.events.ListenableType} src Event source.
  116. * @param {string|Array<string>|
  117. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  118. * type Event type to listen for or array of event types.
  119. * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
  120. * null|undefined} fn Optional callback function to be used as the
  121. * listener or an object with handleEvent function.
  122. * @param {boolean|!AddEventListenerOptions|undefined} options
  123. * @param {T} scope Object in whose scope to call the listener.
  124. * @return {THIS} This object, allowing for chaining of calls.
  125. * @this {THIS}
  126. * @template T, EVENTOBJ, THIS
  127. */
  128. goog.events.EventHandler.prototype.listenWithScope = function(
  129. src, type, fn, options, scope) {
  130. var self = /** @type {!goog.events.EventHandler} */ (this);
  131. // TODO(mknichel): Deprecate this function.
  132. return self.listen_(src, type, fn, options, scope);
  133. };
  134. /**
  135. * Listen to an event on a Listenable. If the function is omitted then the
  136. * EventHandler's handleEvent method will be used.
  137. * @param {goog.events.ListenableType} src Event source.
  138. * @param {string|Array<string>|
  139. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  140. * type Event type to listen for or array of event types.
  141. * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
  142. * Optional callback function to be used as the listener or an object with
  143. * handleEvent function.
  144. * @param {(boolean|!AddEventListenerOptions)=} opt_options
  145. * @param {Object=} opt_scope Object in whose scope to call the listener.
  146. * @return {THIS} This object, allowing for chaining of calls.
  147. * @this {THIS}
  148. * @template EVENTOBJ, THIS
  149. * @private
  150. */
  151. goog.events.EventHandler.prototype.listen_ = function(
  152. src, type, opt_fn, opt_options, opt_scope) {
  153. var self = /** @type {!goog.events.EventHandler} */ (this);
  154. if (!goog.isArray(type)) {
  155. if (type) {
  156. goog.events.EventHandler.typeArray_[0] = type.toString();
  157. }
  158. type = goog.events.EventHandler.typeArray_;
  159. }
  160. for (var i = 0; i < type.length; i++) {
  161. var listenerObj = goog.events.listen(
  162. src, type[i], opt_fn || self.handleEvent, opt_options || false,
  163. opt_scope || self.handler_ || self);
  164. if (!listenerObj) {
  165. // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
  166. // (goog.events.CaptureSimulationMode) in IE8-, it will return null
  167. // value.
  168. return self;
  169. }
  170. var key = listenerObj.key;
  171. self.keys_[key] = listenerObj;
  172. }
  173. return self;
  174. };
  175. /**
  176. * Listen to an event on a Listenable. If the function is omitted, then the
  177. * EventHandler's handleEvent method will be used. After the event has fired the
  178. * event listener is removed from the target. If an array of event types is
  179. * provided, each event type will be listened to once.
  180. * @param {goog.events.ListenableType} src Event source.
  181. * @param {string|Array<string>|
  182. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  183. * type Event type to listen for or array of event types.
  184. * @param {function(this:SCOPE, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
  185. * opt_fn
  186. * Optional callback function to be used as the listener or an object with
  187. * handleEvent function.
  188. * @param {(boolean|!AddEventListenerOptions)=} opt_options
  189. * @return {THIS} This object, allowing for chaining of calls.
  190. * @this {THIS}
  191. * @template EVENTOBJ, THIS
  192. */
  193. goog.events.EventHandler.prototype.listenOnce = function(
  194. src, type, opt_fn, opt_options) {
  195. var self = /** @type {!goog.events.EventHandler} */ (this);
  196. return self.listenOnce_(src, type, opt_fn, opt_options);
  197. };
  198. /**
  199. * Listen to an event on a Listenable. If the function is omitted, then the
  200. * EventHandler's handleEvent method will be used. After the event has fired the
  201. * event listener is removed from the target. If an array of event types is
  202. * provided, each event type will be listened to once.
  203. * @param {goog.events.ListenableType} src Event source.
  204. * @param {string|Array<string>|
  205. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  206. * type Event type to listen for or array of event types.
  207. * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(this:T, ?):?}|
  208. * null|undefined} fn Optional callback function to be used as the
  209. * listener or an object with handleEvent function.
  210. * @param {boolean|undefined} capture Optional whether to use capture phase.
  211. * @param {T} scope Object in whose scope to call the listener.
  212. * @return {THIS} This object, allowing for chaining of calls.
  213. * @this {THIS}
  214. * @template T, EVENTOBJ, THIS
  215. */
  216. goog.events.EventHandler.prototype.listenOnceWithScope = function(
  217. src, type, fn, capture, scope) {
  218. var self = /** @type {!goog.events.EventHandler} */ (this);
  219. // TODO(mknichel): Deprecate this function.
  220. return self.listenOnce_(src, type, fn, capture, scope);
  221. };
  222. /**
  223. * Listen to an event on a Listenable. If the function is omitted, then the
  224. * EventHandler's handleEvent method will be used. After the event has fired
  225. * the event listener is removed from the target. If an array of event types is
  226. * provided, each event type will be listened to once.
  227. * @param {goog.events.ListenableType} src Event source.
  228. * @param {string|Array<string>|
  229. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  230. * type Event type to listen for or array of event types.
  231. * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null=} opt_fn
  232. * Optional callback function to be used as the listener or an object with
  233. * handleEvent function.
  234. * @param {(boolean|!AddEventListenerOptions)=} opt_options
  235. * @param {Object=} opt_scope Object in whose scope to call the listener.
  236. * @return {THIS} This object, allowing for chaining of calls.
  237. * @this {THIS}
  238. * @template EVENTOBJ, THIS
  239. * @private
  240. */
  241. goog.events.EventHandler.prototype.listenOnce_ = function(
  242. src, type, opt_fn, opt_options, opt_scope) {
  243. var self = /** @type {!goog.events.EventHandler} */ (this);
  244. if (goog.isArray(type)) {
  245. for (var i = 0; i < type.length; i++) {
  246. self.listenOnce_(src, type[i], opt_fn, opt_options, opt_scope);
  247. }
  248. } else {
  249. var listenerObj = goog.events.listenOnce(
  250. src, type, opt_fn || self.handleEvent, opt_options,
  251. opt_scope || self.handler_ || self);
  252. if (!listenerObj) {
  253. // When goog.events.listen run on OFF_AND_FAIL or OFF_AND_SILENT
  254. // (goog.events.CaptureSimulationMode) in IE8-, it will return null
  255. // value.
  256. return self;
  257. }
  258. var key = listenerObj.key;
  259. self.keys_[key] = listenerObj;
  260. }
  261. return self;
  262. };
  263. /**
  264. * Adds an event listener with a specific event wrapper on a DOM Node or an
  265. * object that has implemented {@link goog.events.EventTarget}. A listener can
  266. * only be added once to an object.
  267. *
  268. * @param {EventTarget|goog.events.EventTarget} src The node to listen to
  269. * events on.
  270. * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
  271. * @param {function(this:SCOPE, ?):?|{handleEvent:function(?):?}|null} listener
  272. * Callback method, or an object with a handleEvent function.
  273. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
  274. * false).
  275. * @return {THIS} This object, allowing for chaining of calls.
  276. * @this {THIS}
  277. * @template THIS
  278. */
  279. goog.events.EventHandler.prototype.listenWithWrapper = function(
  280. src, wrapper, listener, opt_capt) {
  281. var self = /** @type {!goog.events.EventHandler} */ (this);
  282. // TODO(mknichel): Remove the opt_scope from this function and then
  283. // templatize it.
  284. return self.listenWithWrapper_(src, wrapper, listener, opt_capt);
  285. };
  286. /**
  287. * Adds an event listener with a specific event wrapper on a DOM Node or an
  288. * object that has implemented {@link goog.events.EventTarget}. A listener can
  289. * only be added once to an object.
  290. *
  291. * @param {EventTarget|goog.events.EventTarget} src The node to listen to
  292. * events on.
  293. * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
  294. * @param {function(this:T, ?):?|{handleEvent:function(this:T, ?):?}|null}
  295. * listener Optional callback function to be used as the
  296. * listener or an object with handleEvent function.
  297. * @param {boolean|undefined} capture Optional whether to use capture phase.
  298. * @param {T} scope Object in whose scope to call the listener.
  299. * @return {THIS} This object, allowing for chaining of calls.
  300. * @this {THIS}
  301. * @template T, THIS
  302. */
  303. goog.events.EventHandler.prototype.listenWithWrapperAndScope = function(
  304. src, wrapper, listener, capture, scope) {
  305. var self = /** @type {!goog.events.EventHandler} */ (this);
  306. // TODO(mknichel): Deprecate this function.
  307. return self.listenWithWrapper_(src, wrapper, listener, capture, scope);
  308. };
  309. /**
  310. * Adds an event listener with a specific event wrapper on a DOM Node or an
  311. * object that has implemented {@link goog.events.EventTarget}. A listener can
  312. * only be added once to an object.
  313. *
  314. * @param {EventTarget|goog.events.EventTarget} src The node to listen to
  315. * events on.
  316. * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
  317. * @param {function(?):?|{handleEvent:function(?):?}|null} listener Callback
  318. * method, or an object with a handleEvent function.
  319. * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
  320. * false).
  321. * @param {Object=} opt_scope Element in whose scope to call the listener.
  322. * @return {THIS} This object, allowing for chaining of calls.
  323. * @this {THIS}
  324. * @template THIS
  325. * @private
  326. */
  327. goog.events.EventHandler.prototype.listenWithWrapper_ = function(
  328. src, wrapper, listener, opt_capt, opt_scope) {
  329. var self = /** @type {!goog.events.EventHandler} */ (this);
  330. wrapper.listen(
  331. src, listener, opt_capt, opt_scope || self.handler_ || self, self);
  332. return self;
  333. };
  334. /**
  335. * @return {number} Number of listeners registered by this handler.
  336. */
  337. goog.events.EventHandler.prototype.getListenerCount = function() {
  338. var count = 0;
  339. for (var key in this.keys_) {
  340. if (Object.prototype.hasOwnProperty.call(this.keys_, key)) {
  341. count++;
  342. }
  343. }
  344. return count;
  345. };
  346. /**
  347. * Unlistens on an event.
  348. * @param {goog.events.ListenableType} src Event source.
  349. * @param {string|Array<string>|
  350. * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
  351. * type Event type or array of event types to unlisten to.
  352. * @param {function(this:?, EVENTOBJ):?|{handleEvent:function(?):?}|null=}
  353. * opt_fn Optional callback function to be used as the listener or an object
  354. * with handleEvent function.
  355. * @param {(boolean|!EventListenerOptions)=} opt_options
  356. * @param {Object=} opt_scope Object in whose scope to call the listener.
  357. * @return {THIS} This object, allowing for chaining of calls.
  358. * @this {THIS}
  359. * @template EVENTOBJ, THIS
  360. */
  361. goog.events.EventHandler.prototype.unlisten = function(
  362. src, type, opt_fn, opt_options, opt_scope) {
  363. var self = /** @type {!goog.events.EventHandler} */ (this);
  364. if (goog.isArray(type)) {
  365. for (var i = 0; i < type.length; i++) {
  366. self.unlisten(src, type[i], opt_fn, opt_options, opt_scope);
  367. }
  368. } else {
  369. var capture =
  370. goog.isObject(opt_options) ? !!opt_options.capture : !!opt_options;
  371. var listener = goog.events.getListener(
  372. src, type, opt_fn || self.handleEvent, capture,
  373. opt_scope || self.handler_ || self);
  374. if (listener) {
  375. goog.events.unlistenByKey(listener);
  376. delete self.keys_[listener.key];
  377. }
  378. }
  379. return self;
  380. };
  381. /**
  382. * Removes an event listener which was added with listenWithWrapper().
  383. *
  384. * @param {EventTarget|goog.events.EventTarget} src The target to stop
  385. * listening to events on.
  386. * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
  387. * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
  388. * listener function to remove.
  389. * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
  390. * whether the listener is fired during the capture or bubble phase of the
  391. * event.
  392. * @param {Object=} opt_scope Element in whose scope to call the listener.
  393. * @return {THIS} This object, allowing for chaining of calls.
  394. * @this {THIS}
  395. * @template THIS
  396. */
  397. goog.events.EventHandler.prototype.unlistenWithWrapper = function(
  398. src, wrapper, listener, opt_capt, opt_scope) {
  399. var self = /** @type {!goog.events.EventHandler} */ (this);
  400. wrapper.unlisten(
  401. src, listener, opt_capt, opt_scope || self.handler_ || self, self);
  402. return self;
  403. };
  404. /**
  405. * Unlistens to all events.
  406. */
  407. goog.events.EventHandler.prototype.removeAll = function() {
  408. goog.object.forEach(this.keys_, function(listenerObj, key) {
  409. if (this.keys_.hasOwnProperty(key)) {
  410. goog.events.unlistenByKey(listenerObj);
  411. }
  412. }, this);
  413. this.keys_ = {};
  414. };
  415. /**
  416. * Disposes of this EventHandler and removes all listeners that it registered.
  417. * @override
  418. * @protected
  419. */
  420. goog.events.EventHandler.prototype.disposeInternal = function() {
  421. goog.events.EventHandler.superClass_.disposeInternal.call(this);
  422. this.removeAll();
  423. };
  424. /**
  425. * Default event handler
  426. * @param {goog.events.Event} e Event object.
  427. */
  428. goog.events.EventHandler.prototype.handleEvent = function(e) {
  429. throw Error('EventHandler.handleEvent not implemented');
  430. };