eventManager.js 8.0 KB

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