DirectEditing.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import {
  2. bind,
  3. find
  4. } from 'min-dash';
  5. import TextBox from './TextBox';
  6. /**
  7. * A direct editing component that allows users
  8. * to edit an elements text directly in the diagram
  9. *
  10. * @param {EventBus} eventBus the event bus
  11. */
  12. export default function DirectEditing(eventBus, canvas) {
  13. this._eventBus = eventBus;
  14. this._providers = [];
  15. this._textbox = new TextBox({
  16. container: canvas.getContainer(),
  17. keyHandler: bind(this._handleKey, this),
  18. resizeHandler: bind(this._handleResize, this)
  19. });
  20. }
  21. DirectEditing.$inject = [ 'eventBus', 'canvas' ];
  22. /**
  23. * Register a direct editing provider
  24. * @param {Object} provider the provider, must expose an #activate(element) method that returns
  25. * an activation context ({ bounds: {x, y, width, height }, text }) if
  26. * direct editing is available for the given element.
  27. * Additionally the provider must expose a #update(element, value) method
  28. * to receive direct editing updates.
  29. */
  30. DirectEditing.prototype.registerProvider = function(provider) {
  31. this._providers.push(provider);
  32. };
  33. /**
  34. * Returns true if direct editing is currently active
  35. *
  36. * @return {Boolean}
  37. */
  38. DirectEditing.prototype.isActive = function() {
  39. return !!this._active;
  40. };
  41. /**
  42. * Cancel direct editing, if it is currently active
  43. */
  44. DirectEditing.prototype.cancel = function() {
  45. if (!this._active) {
  46. return;
  47. }
  48. this._fire('cancel');
  49. this.close();
  50. };
  51. DirectEditing.prototype._fire = function(event, context) {
  52. this._eventBus.fire('directEditing.' + event, context || { active: this._active });
  53. };
  54. DirectEditing.prototype.close = function() {
  55. this._textbox.destroy();
  56. this._fire('deactivate');
  57. this._active = null;
  58. this.resizable = undefined;
  59. };
  60. DirectEditing.prototype.complete = function() {
  61. var active = this._active;
  62. if (!active) {
  63. return;
  64. }
  65. var containerBounds,
  66. previousBounds = active.context.bounds,
  67. newBounds = this.$textbox.getBoundingClientRect(),
  68. newText = this.getValue(),
  69. previousText = active.context.text;
  70. if (
  71. newText !== previousText ||
  72. newBounds.height !== previousBounds.height ||
  73. newBounds.width !== previousBounds.width
  74. ) {
  75. containerBounds = this._textbox.container.getBoundingClientRect();
  76. active.provider.update(active.element, newText, active.context.text, {
  77. x: newBounds.left - containerBounds.left,
  78. y: newBounds.top - containerBounds.top,
  79. width: newBounds.width,
  80. height: newBounds.height
  81. });
  82. }
  83. this._fire('complete');
  84. this.close();
  85. };
  86. DirectEditing.prototype.getValue = function() {
  87. return this._textbox.getValue();
  88. };
  89. DirectEditing.prototype._handleKey = function(e) {
  90. // stop bubble
  91. e.stopPropagation();
  92. var key = e.keyCode || e.charCode;
  93. // ESC
  94. if (key === 27) {
  95. e.preventDefault();
  96. return this.cancel();
  97. }
  98. // Enter
  99. if (key === 13 && !e.shiftKey) {
  100. e.preventDefault();
  101. return this.complete();
  102. }
  103. };
  104. DirectEditing.prototype._handleResize = function(event) {
  105. this._fire('resize', event);
  106. };
  107. /**
  108. * Activate direct editing on the given element
  109. *
  110. * @param {Object} ElementDescriptor the descriptor for a shape or connection
  111. * @return {Boolean} true if the activation was possible
  112. */
  113. DirectEditing.prototype.activate = function(element) {
  114. if (this.isActive()) {
  115. this.cancel();
  116. }
  117. // the direct editing context
  118. var context;
  119. var provider = find(this._providers, function(p) {
  120. return (context = p.activate(element)) ? p : null;
  121. });
  122. // check if activation took place
  123. if (context) {
  124. this.$textbox = this._textbox.create(
  125. context.bounds,
  126. context.style,
  127. context.text,
  128. context.options
  129. );
  130. this._active = {
  131. element: element,
  132. context: context,
  133. provider: provider
  134. };
  135. if (context.options && context.options.resizable) {
  136. this.resizable = true;
  137. }
  138. this._fire('activate');
  139. }
  140. return !!context;
  141. };