2a1271413bc836bdf63f12fb42920666ac48505d3b723660c55851afdf9b3c3189d60abf9814bc4f669ba36a7bf6f4b6701bd285c4a766497a1a164c3f8d33 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // import Core from './core';
  2. import {polymerWrap, closest} from './helpers/dom/element';
  3. import {isWebComponentSupportedNatively} from './helpers/feature';
  4. import {stopImmediatePropagation as _stopImmediatePropagation} from './helpers/dom/event';
  5. /**
  6. * Counter which tracks unregistered listeners (useful for detecting memory leaks).
  7. *
  8. * @type {Number}
  9. */
  10. let listenersCounter = 0;
  11. /**
  12. * Event DOM manager for internal use in Handsontable.
  13. *
  14. * @class EventManager
  15. * @util
  16. */
  17. class EventManager {
  18. /**
  19. * @param {Object} [context=null]
  20. * @private
  21. */
  22. constructor(context = null) {
  23. this.context = context || this;
  24. if (!this.context.eventListeners) {
  25. this.context.eventListeners = [];
  26. }
  27. }
  28. /**
  29. * Register specified listener (`eventName`) to the element.
  30. *
  31. * @param {Element} element Target element.
  32. * @param {String} eventName Event name.
  33. * @param {Function} callback Function which will be called after event occur.
  34. * @returns {Function} Returns function which you can easily call to remove that event
  35. */
  36. addEventListener(element, eventName, callback) {
  37. let context = this.context;
  38. function callbackProxy(event) {
  39. event = extendEvent(context, event);
  40. callback.call(this, event);
  41. }
  42. this.context.eventListeners.push({
  43. element,
  44. event: eventName,
  45. callback,
  46. callbackProxy,
  47. });
  48. if (window.addEventListener) {
  49. element.addEventListener(eventName, callbackProxy, false);
  50. } else {
  51. element.attachEvent(`on${eventName}`, callbackProxy);
  52. }
  53. listenersCounter++;
  54. return () => {
  55. this.removeEventListener(element, eventName, callback);
  56. };
  57. }
  58. /**
  59. * Remove the event listener previously registered.
  60. *
  61. * @param {Element} element Target element.
  62. * @param {String} eventName Event name.
  63. * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener.
  64. */
  65. removeEventListener(element, eventName, callback) {
  66. let len = this.context.eventListeners.length;
  67. let tmpEvent;
  68. while (len--) {
  69. tmpEvent = this.context.eventListeners[len];
  70. if (tmpEvent.event == eventName && tmpEvent.element == element) {
  71. if (callback && callback != tmpEvent.callback) {
  72. /* eslint-disable no-continue */
  73. continue;
  74. }
  75. this.context.eventListeners.splice(len, 1);
  76. if (tmpEvent.element.removeEventListener) {
  77. tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, false);
  78. } else {
  79. tmpEvent.element.detachEvent(`on${tmpEvent.event}`, tmpEvent.callbackProxy);
  80. }
  81. listenersCounter--;
  82. }
  83. }
  84. }
  85. /**
  86. * Clear all previously registered events.
  87. *
  88. * @private
  89. * @since 0.15.0-beta3
  90. */
  91. clearEvents() {
  92. if (!this.context) {
  93. return;
  94. }
  95. let len = this.context.eventListeners.length;
  96. while (len--) {
  97. let event = this.context.eventListeners[len];
  98. if (event) {
  99. this.removeEventListener(event.element, event.event, event.callback);
  100. }
  101. }
  102. }
  103. /**
  104. * Clear all previously registered events.
  105. */
  106. clear() {
  107. this.clearEvents();
  108. }
  109. /**
  110. * Destroy instance of EventManager.
  111. */
  112. destroy() {
  113. this.clearEvents();
  114. this.context = null;
  115. }
  116. /**
  117. * Trigger event at the specified target element.
  118. *
  119. * @param {Element} element Target element.
  120. * @param {String} eventName Event name.
  121. */
  122. fireEvent(element, eventName) {
  123. let options = {
  124. bubbles: true,
  125. cancelable: (eventName !== 'mousemove'),
  126. view: window,
  127. detail: 0,
  128. screenX: 0,
  129. screenY: 0,
  130. clientX: 1,
  131. clientY: 1,
  132. ctrlKey: false,
  133. altKey: false,
  134. shiftKey: false,
  135. metaKey: false,
  136. button: 0,
  137. relatedTarget: undefined,
  138. };
  139. var event;
  140. if (document.createEvent) {
  141. event = document.createEvent('MouseEvents');
  142. event.initMouseEvent(eventName, options.bubbles, options.cancelable,
  143. options.view, options.detail,
  144. options.screenX, options.screenY, options.clientX, options.clientY,
  145. options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
  146. options.button, options.relatedTarget || document.body.parentNode);
  147. } else {
  148. event = document.createEventObject();
  149. }
  150. if (element.dispatchEvent) {
  151. element.dispatchEvent(event);
  152. } else {
  153. element.fireEvent(`on${eventName}`, event);
  154. }
  155. }
  156. }
  157. /**
  158. * @param {Object} context
  159. * @param {Event} event
  160. * @private
  161. * @returns {*}
  162. */
  163. function extendEvent(context, event) {
  164. let componentName = 'HOT-TABLE';
  165. let isHotTableSpotted;
  166. let fromElement;
  167. let realTarget;
  168. let target;
  169. let len;
  170. let nativeStopImmediatePropagation;
  171. event.isTargetWebComponent = false;
  172. event.realTarget = event.target;
  173. nativeStopImmediatePropagation = event.stopImmediatePropagation;
  174. event.stopImmediatePropagation = function() {
  175. nativeStopImmediatePropagation.apply(this);
  176. _stopImmediatePropagation(this);
  177. };
  178. if (!EventManager.isHotTableEnv) {
  179. return event;
  180. }
  181. event = polymerWrap(event);
  182. len = event.path ? event.path.length : 0;
  183. while (len--) {
  184. if (event.path[len].nodeName === componentName) {
  185. isHotTableSpotted = true;
  186. } else if (isHotTableSpotted && event.path[len].shadowRoot) {
  187. target = event.path[len];
  188. break;
  189. }
  190. if (len === 0 && !target) {
  191. target = event.path[len];
  192. }
  193. }
  194. if (!target) {
  195. target = event.target;
  196. }
  197. event.isTargetWebComponent = true;
  198. if (isWebComponentSupportedNatively()) {
  199. event.realTarget = event.srcElement || event.toElement;
  200. } else if (context instanceof Core || context instanceof Walkontable) {
  201. // Polymer doesn't support `event.target` property properly we must emulate it ourselves
  202. if (context instanceof Core) {
  203. fromElement = context.view ? context.view.wt.wtTable.TABLE : null;
  204. } else if (context instanceof Walkontable) {
  205. // .wtHider
  206. fromElement = context.wtTable.TABLE.parentNode.parentNode;
  207. }
  208. realTarget = closest(event.target, [componentName], fromElement);
  209. if (realTarget) {
  210. event.realTarget = fromElement.querySelector(componentName) || event.target;
  211. } else {
  212. event.realTarget = event.target;
  213. }
  214. }
  215. Object.defineProperty(event, 'target', {
  216. get() {
  217. return polymerWrap(target);
  218. },
  219. enumerable: true,
  220. configurable: true,
  221. });
  222. return event;
  223. }
  224. export default EventManager;
  225. export function getListenersCounter() {
  226. return listenersCounter;
  227. };