BpmnImporter.js 8.0 KB

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