vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
4.2 KiB

  1. var keycharm = require('keycharm');
  2. var Emitter = require('emitter-component');
  3. var Hammer = require('../module/hammer');
  4. var util = require('../util');
  5. /**
  6. * Turn an element into an clickToUse element.
  7. * When not active, the element has a transparent overlay. When the overlay is
  8. * clicked, the mode is changed to active.
  9. * When active, the element is displayed with a blue border around it, and
  10. * the interactive contents of the element can be used. When clicked outside
  11. * the element, the elements mode is changed to inactive.
  12. * @param {Element} container
  13. * @constructor Activator
  14. */
  15. function Activator(container) {
  16. this.active = false;
  17. this.dom = {
  18. container: container
  19. };
  20. this.dom.overlay = document.createElement('div');
  21. this.dom.overlay.className = 'vis-overlay';
  22. this.dom.container.appendChild(this.dom.overlay);
  23. this.hammer = Hammer(this.dom.overlay);
  24. this.hammer.on('tap', this._onTapOverlay.bind(this));
  25. // block all touch events (except tap)
  26. var me = this;
  27. var events = [
  28. 'tap', 'doubletap', 'press',
  29. 'pinch',
  30. 'pan', 'panstart', 'panmove', 'panend'
  31. ];
  32. events.forEach(function (event) {
  33. me.hammer.on(event, function (event) {
  34. event.stopPropagation();
  35. });
  36. });
  37. // attach a click event to the window, in order to deactivate when clicking outside the timeline
  38. if (document && document.body) {
  39. this.onClick = function (event) {
  40. if (!_hasParent(event.target, container)) {
  41. me.deactivate();
  42. }
  43. };
  44. document.body.addEventListener('click', this.onClick);
  45. }
  46. if (this.keycharm !== undefined) {
  47. this.keycharm.destroy();
  48. }
  49. this.keycharm = keycharm();
  50. // keycharm listener only bounded when active)
  51. this.escListener = this.deactivate.bind(this);
  52. }
  53. // turn into an event emitter
  54. Emitter(Activator.prototype);
  55. // The currently active activator
  56. Activator.current = null;
  57. /**
  58. * Destroy the activator. Cleans up all created DOM and event listeners
  59. */
  60. Activator.prototype.destroy = function () {
  61. this.deactivate();
  62. // remove dom
  63. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  64. // remove global event listener
  65. if (this.onClick) {
  66. document.body.removeEventListener('click', this.onClick);
  67. }
  68. // remove keycharm
  69. if (this.keycharm !== undefined) {
  70. this.keycharm.destroy();
  71. }
  72. this.keycharm = null;
  73. // cleanup hammer instances
  74. this.hammer.destroy();
  75. this.hammer = null;
  76. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  77. };
  78. /**
  79. * Activate the element
  80. * Overlay is hidden, element is decorated with a blue shadow border
  81. */
  82. Activator.prototype.activate = function () {
  83. // we allow only one active activator at a time
  84. if (Activator.current) {
  85. Activator.current.deactivate();
  86. }
  87. Activator.current = this;
  88. this.active = true;
  89. this.dom.overlay.style.display = 'none';
  90. util.addClassName(this.dom.container, 'vis-active');
  91. this.emit('change');
  92. this.emit('activate');
  93. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  94. // keyboard events on a 'change' event
  95. this.keycharm.bind('esc', this.escListener);
  96. };
  97. /**
  98. * Deactivate the element
  99. * Overlay is displayed on top of the element
  100. */
  101. Activator.prototype.deactivate = function () {
  102. this.active = false;
  103. this.dom.overlay.style.display = '';
  104. util.removeClassName(this.dom.container, 'vis-active');
  105. this.keycharm.unbind('esc', this.escListener);
  106. this.emit('change');
  107. this.emit('deactivate');
  108. };
  109. /**
  110. * Handle a tap event: activate the container
  111. * @param {Event} event The event
  112. * @private
  113. */
  114. Activator.prototype._onTapOverlay = function (event) {
  115. // activate the container
  116. this.activate();
  117. event.stopPropagation();
  118. };
  119. /**
  120. * Test whether the element has the requested parent element somewhere in
  121. * its chain of parent nodes.
  122. * @param {HTMLElement} element
  123. * @param {HTMLElement} parent
  124. * @returns {boolean} Returns true when the parent is found somewhere in the
  125. * chain of parent nodes.
  126. * @private
  127. */
  128. function _hasParent(element, parent) {
  129. while (element) {
  130. if (element === parent) {
  131. return true
  132. }
  133. element = element.parentNode;
  134. }
  135. return false;
  136. }
  137. module.exports = Activator;