GraphicsFactory.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import {
  2. forEach,
  3. reduce
  4. } from 'min-dash';
  5. import {
  6. getChildren,
  7. getVisual
  8. } from '../util/GraphicsUtil';
  9. import { translate } from '../util/SvgTransformUtil';
  10. import { clear as domClear } from 'min-dom';
  11. import {
  12. append as svgAppend,
  13. attr as svgAttr,
  14. classes as svgClasses,
  15. create as svgCreate,
  16. remove as svgRemove
  17. } from 'tiny-svg';
  18. import {
  19. isFrameElement
  20. } from '../util/Elements';
  21. /**
  22. * A factory that creates graphical elements
  23. *
  24. * @param {EventBus} eventBus
  25. * @param {ElementRegistry} elementRegistry
  26. */
  27. export default function GraphicsFactory(eventBus, elementRegistry) {
  28. this._eventBus = eventBus;
  29. this._elementRegistry = elementRegistry;
  30. }
  31. GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ];
  32. GraphicsFactory.prototype._getChildrenContainer = function(element) {
  33. var gfx = this._elementRegistry.getGraphics(element);
  34. var childrenGfx;
  35. // root element
  36. if (!element.parent) {
  37. childrenGfx = gfx;
  38. } else {
  39. childrenGfx = getChildren(gfx);
  40. if (!childrenGfx) {
  41. childrenGfx = svgCreate('g');
  42. svgClasses(childrenGfx).add('djs-children');
  43. svgAppend(gfx.parentNode, childrenGfx);
  44. }
  45. }
  46. return childrenGfx;
  47. };
  48. /**
  49. * Clears the graphical representation of the element and returns the
  50. * cleared visual (the <g class="djs-visual" /> element).
  51. */
  52. GraphicsFactory.prototype._clear = function(gfx) {
  53. var visual = getVisual(gfx);
  54. domClear(visual);
  55. return visual;
  56. };
  57. /**
  58. * Creates a gfx container for shapes and connections
  59. *
  60. * The layout is as follows:
  61. *
  62. * <g class="djs-group">
  63. *
  64. * <!-- the gfx -->
  65. * <g class="djs-element djs-(shape|connection|frame)">
  66. * <g class="djs-visual">
  67. * <!-- the renderer draws in here -->
  68. * </g>
  69. *
  70. * <!-- extensions (overlays, click box, ...) goes here
  71. * </g>
  72. *
  73. * <!-- the gfx child nodes -->
  74. * <g class="djs-children"></g>
  75. * </g>
  76. *
  77. * @param {string} type the type of the element, i.e. shape | connection
  78. * @param {SVGElement} [childrenGfx]
  79. * @param {number} [parentIndex] position to create container in parent
  80. * @param {boolean} [isFrame] is frame element
  81. *
  82. * @return {SVGElement}
  83. */
  84. GraphicsFactory.prototype._createContainer = function(
  85. type, childrenGfx, parentIndex, isFrame
  86. ) {
  87. var outerGfx = svgCreate('g');
  88. svgClasses(outerGfx).add('djs-group');
  89. // insert node at position
  90. if (typeof parentIndex !== 'undefined') {
  91. prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]);
  92. } else {
  93. svgAppend(childrenGfx, outerGfx);
  94. }
  95. var gfx = svgCreate('g');
  96. svgClasses(gfx).add('djs-element');
  97. svgClasses(gfx).add('djs-' + type);
  98. if (isFrame) {
  99. svgClasses(gfx).add('djs-frame');
  100. }
  101. svgAppend(outerGfx, gfx);
  102. // create visual
  103. var visual = svgCreate('g');
  104. svgClasses(visual).add('djs-visual');
  105. svgAppend(gfx, visual);
  106. return gfx;
  107. };
  108. GraphicsFactory.prototype.create = function(type, element, parentIndex) {
  109. var childrenGfx = this._getChildrenContainer(element.parent);
  110. return this._createContainer(type, childrenGfx, parentIndex, isFrameElement(element));
  111. };
  112. GraphicsFactory.prototype.updateContainments = function(elements) {
  113. var self = this,
  114. elementRegistry = this._elementRegistry,
  115. parents;
  116. parents = reduce(elements, function(map, e) {
  117. if (e.parent) {
  118. map[e.parent.id] = e.parent;
  119. }
  120. return map;
  121. }, {});
  122. // update all parents of changed and reorganized their children
  123. // in the correct order (as indicated in our model)
  124. forEach(parents, function(parent) {
  125. var children = parent.children;
  126. if (!children) {
  127. return;
  128. }
  129. var childrenGfx = self._getChildrenContainer(parent);
  130. forEach(children.slice().reverse(), function(child) {
  131. var childGfx = elementRegistry.getGraphics(child);
  132. prependTo(childGfx.parentNode, childrenGfx);
  133. });
  134. });
  135. };
  136. GraphicsFactory.prototype.drawShape = function(visual, element) {
  137. var eventBus = this._eventBus;
  138. return eventBus.fire('render.shape', { gfx: visual, element: element });
  139. };
  140. GraphicsFactory.prototype.getShapePath = function(element) {
  141. var eventBus = this._eventBus;
  142. return eventBus.fire('render.getShapePath', element);
  143. };
  144. GraphicsFactory.prototype.drawConnection = function(visual, element) {
  145. var eventBus = this._eventBus;
  146. return eventBus.fire('render.connection', { gfx: visual, element: element });
  147. };
  148. GraphicsFactory.prototype.getConnectionPath = function(waypoints) {
  149. var eventBus = this._eventBus;
  150. return eventBus.fire('render.getConnectionPath', waypoints);
  151. };
  152. GraphicsFactory.prototype.update = function(type, element, gfx) {
  153. // do NOT update root element
  154. if (!element.parent) {
  155. return;
  156. }
  157. var visual = this._clear(gfx);
  158. // redraw
  159. if (type === 'shape') {
  160. this.drawShape(visual, element);
  161. // update positioning
  162. translate(gfx, element.x, element.y);
  163. } else
  164. if (type === 'connection') {
  165. this.drawConnection(visual, element);
  166. } else {
  167. throw new Error('unknown type: ' + type);
  168. }
  169. if (element.hidden) {
  170. svgAttr(gfx, 'display', 'none');
  171. } else {
  172. svgAttr(gfx, 'display', 'block');
  173. }
  174. };
  175. GraphicsFactory.prototype.remove = function(element) {
  176. var gfx = this._elementRegistry.getGraphics(element);
  177. // remove
  178. svgRemove(gfx.parentNode);
  179. };
  180. // helpers //////////
  181. function prependTo(newNode, parentNode, siblingNode) {
  182. var node = siblingNode || parentNode.firstChild;
  183. // do not prepend node to itself to prevent IE from crashing
  184. // https://github.com/bpmn-io/bpmn-js/issues/746
  185. if (newNode === node) {
  186. return;
  187. }
  188. parentNode.insertBefore(newNode, node);
  189. }