BpmnImporter.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import {
  2. assign
  3. } from 'min-dash';
  4. import { is } from '../util/ModelUtil';
  5. import {
  6. isLabelExternal,
  7. getExternalLabelBounds
  8. } from '../util/LabelUtil';
  9. import {
  10. getMid
  11. } from 'diagram-js/lib/layout/LayoutUtil';
  12. import {
  13. isExpanded
  14. } from '../util/DiUtil';
  15. import {
  16. elementToString
  17. } from './Util';
  18. function elementData(semantic, attrs) {
  19. return assign({
  20. id: semantic.id,
  21. type: semantic.$type,
  22. businessObject: semantic
  23. }, attrs);
  24. }
  25. function getWaypoints(bo, source, target) {
  26. var waypoints = bo.di.waypoint;
  27. if (!waypoints || waypoints.length < 2) {
  28. return [ getMid(source), getMid(target) ];
  29. }
  30. return waypoints.map(function(p) {
  31. return { x: p.x, y: p.y };
  32. });
  33. }
  34. function notYetDrawn(translate, semantic, refSemantic, property) {
  35. return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', {
  36. element: elementToString(refSemantic),
  37. referenced: elementToString(semantic),
  38. property: property
  39. }));
  40. }
  41. /**
  42. * An importer that adds bpmn elements to the canvas
  43. *
  44. * @param {EventBus} eventBus
  45. * @param {Canvas} canvas
  46. * @param {ElementFactory} elementFactory
  47. * @param {ElementRegistry} elementRegistry
  48. * @param {Function} translate
  49. * @param {TextRenderer} textRenderer
  50. */
  51. export default function BpmnImporter(
  52. eventBus, canvas, elementFactory,
  53. elementRegistry, translate, textRenderer) {
  54. this._eventBus = eventBus;
  55. this._canvas = canvas;
  56. this._elementFactory = elementFactory;
  57. this._elementRegistry = elementRegistry;
  58. this._translate = translate;
  59. this._textRenderer = textRenderer;
  60. }
  61. BpmnImporter.$inject = [
  62. 'eventBus',
  63. 'canvas',
  64. 'elementFactory',
  65. 'elementRegistry',
  66. 'translate',
  67. 'textRenderer'
  68. ];
  69. /**
  70. * Add bpmn element (semantic) to the canvas onto the
  71. * specified parent shape.
  72. */
  73. BpmnImporter.prototype.add = function(semantic, parentElement) {
  74. var di = semantic.di,
  75. element,
  76. translate = this._translate,
  77. hidden;
  78. var parentIndex;
  79. // ROOT ELEMENT
  80. // handle the special case that we deal with a
  81. // invisible root element (process or collaboration)
  82. if (is(di, 'bpmndi:BPMNPlane')) {
  83. // add a virtual element (not being drawn)
  84. element = this._elementFactory.createRoot(elementData(semantic));
  85. this._canvas.setRootElement(element);
  86. }
  87. // SHAPE
  88. else if (is(di, 'bpmndi:BPMNShape')) {
  89. var collapsed = !isExpanded(semantic);
  90. hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
  91. var bounds = semantic.di.bounds;
  92. element = this._elementFactory.createShape(elementData(semantic, {
  93. collapsed: collapsed,
  94. hidden: hidden,
  95. x: Math.round(bounds.x),
  96. y: Math.round(bounds.y),
  97. width: Math.round(bounds.width),
  98. height: Math.round(bounds.height)
  99. }));
  100. if (is(semantic, 'bpmn:BoundaryEvent')) {
  101. this._attachBoundary(semantic, element);
  102. }
  103. // insert lanes behind other flow nodes (cf. #727)
  104. if (is(semantic, 'bpmn:Lane')) {
  105. parentIndex = 0;
  106. }
  107. if (is(semantic, 'bpmn:DataStoreReference')) {
  108. // check wether data store is inside our outside of its semantic parent
  109. if (!isPointInsideBBox(parentElement, getMid(bounds))) {
  110. parentElement = this._canvas.getRootElement();
  111. }
  112. }
  113. this._canvas.addShape(element, parentElement, parentIndex);
  114. }
  115. // CONNECTION
  116. else if (is(di, 'bpmndi:BPMNEdge')) {
  117. var source = this._getSource(semantic),
  118. target = this._getTarget(semantic);
  119. hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
  120. element = this._elementFactory.createConnection(elementData(semantic, {
  121. hidden: hidden,
  122. source: source,
  123. target: target,
  124. waypoints: getWaypoints(semantic, source, target)
  125. }));
  126. if (is(semantic, 'bpmn:DataAssociation')) {
  127. // render always on top; this ensures DataAssociations
  128. // are rendered correctly across different "hacks" people
  129. // love to model such as cross participant / sub process
  130. // associations
  131. parentElement = null;
  132. }
  133. // insert sequence flows behind other flow nodes (cf. #727)
  134. if (is(semantic, 'bpmn:SequenceFlow')) {
  135. parentIndex = 0;
  136. }
  137. this._canvas.addConnection(element, parentElement, parentIndex);
  138. } else {
  139. throw new Error(translate('unknown di {di} for element {semantic}', {
  140. di: elementToString(di),
  141. semantic: elementToString(semantic)
  142. }));
  143. }
  144. // (optional) LABEL
  145. if (isLabelExternal(semantic) && semantic.name) {
  146. this.addLabel(semantic, element);
  147. }
  148. this._eventBus.fire('bpmnElement.added', { element: element });
  149. return element;
  150. };
  151. /**
  152. * Attach the boundary element to the given host
  153. *
  154. * @param {ModdleElement} boundarySemantic
  155. * @param {djs.model.Base} boundaryElement
  156. */
  157. BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) {
  158. var translate = this._translate;
  159. var hostSemantic = boundarySemantic.attachedToRef;
  160. if (!hostSemantic) {
  161. throw new Error(translate('missing {semantic}#attachedToRef', {
  162. semantic: elementToString(boundarySemantic)
  163. }));
  164. }
  165. var host = this._elementRegistry.get(hostSemantic.id),
  166. attachers = host && host.attachers;
  167. if (!host) {
  168. throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef');
  169. }
  170. // wire element.host <> host.attachers
  171. boundaryElement.host = host;
  172. if (!attachers) {
  173. host.attachers = attachers = [];
  174. }
  175. if (attachers.indexOf(boundaryElement) === -1) {
  176. attachers.push(boundaryElement);
  177. }
  178. };
  179. /**
  180. * add label for an element
  181. */
  182. BpmnImporter.prototype.addLabel = function(semantic, element) {
  183. var bounds,
  184. text,
  185. label;
  186. bounds = getExternalLabelBounds(semantic, element);
  187. text = semantic.name;
  188. if (text) {
  189. // get corrected bounds from actual layouted text
  190. bounds = this._textRenderer.getExternalLabelBounds(bounds, text);
  191. }
  192. label = this._elementFactory.createLabel(elementData(semantic, {
  193. id: semantic.id + '_label',
  194. labelTarget: element,
  195. type: 'label',
  196. hidden: element.hidden || !semantic.name,
  197. x: Math.round(bounds.x),
  198. y: Math.round(bounds.y),
  199. width: Math.round(bounds.width),
  200. height: Math.round(bounds.height)
  201. }));
  202. return this._canvas.addShape(label, element.parent);
  203. };
  204. /**
  205. * Return the drawn connection end based on the given side.
  206. *
  207. * @throws {Error} if the end is not yet drawn
  208. */
  209. BpmnImporter.prototype._getEnd = function(semantic, side) {
  210. var element,
  211. refSemantic,
  212. type = semantic.$type,
  213. translate = this._translate;
  214. refSemantic = semantic[side + 'Ref'];
  215. // handle mysterious isMany DataAssociation#sourceRef
  216. if (side === 'source' && type === 'bpmn:DataInputAssociation') {
  217. refSemantic = refSemantic && refSemantic[0];
  218. }
  219. // fix source / target for DataInputAssociation / DataOutputAssociation
  220. if (side === 'source' && type === 'bpmn:DataOutputAssociation' ||
  221. side === 'target' && type === 'bpmn:DataInputAssociation') {
  222. refSemantic = semantic.$parent;
  223. }
  224. element = refSemantic && this._getElement(refSemantic);
  225. if (element) {
  226. return element;
  227. }
  228. if (refSemantic) {
  229. throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref');
  230. } else {
  231. throw new Error(translate('{semantic}#{side} Ref not specified', {
  232. semantic: elementToString(semantic),
  233. side: side
  234. }));
  235. }
  236. };
  237. BpmnImporter.prototype._getSource = function(semantic) {
  238. return this._getEnd(semantic, 'source');
  239. };
  240. BpmnImporter.prototype._getTarget = function(semantic) {
  241. return this._getEnd(semantic, 'target');
  242. };
  243. BpmnImporter.prototype._getElement = function(semantic) {
  244. return this._elementRegistry.get(semantic.id);
  245. };
  246. // helpers ////////////////////
  247. function isPointInsideBBox(bbox, point) {
  248. var x = point.x,
  249. y = point.y;
  250. return x >= bbox.x &&
  251. x <= bbox.x + bbox.width &&
  252. y >= bbox.y &&
  253. y <= bbox.y + bbox.height;
  254. }