BpmnReplace.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import {
  2. pick,
  3. assign,
  4. filter,
  5. has
  6. } from 'min-dash';
  7. import {
  8. is,
  9. getBusinessObject
  10. } from '../../util/ModelUtil';
  11. import {
  12. isAny
  13. } from '../modeling/util/ModelingUtil';
  14. import {
  15. isExpanded,
  16. isEventSubProcess
  17. } from '../../util/DiUtil';
  18. import {
  19. getProperties,
  20. IGNORED_PROPERTIES
  21. } from '../../util/model/ModelCloneUtils';
  22. import ModelCloneHelper from '../../util/model/ModelCloneHelper';
  23. var CUSTOM_PROPERTIES = [
  24. 'cancelActivity',
  25. 'instantiate',
  26. 'eventGatewayType',
  27. 'triggeredByEvent',
  28. 'isInterrupting'
  29. ];
  30. function toggeling(element, target) {
  31. var oldCollapsed = (
  32. element && has(element, 'collapsed') ? element.collapsed : !isExpanded(element)
  33. );
  34. var targetCollapsed;
  35. if (target && (has(target, 'collapsed') || has(target, 'isExpanded'))) {
  36. // property is explicitly set so use it
  37. targetCollapsed = (
  38. has(target, 'collapsed') ? target.collapsed : !target.isExpanded
  39. );
  40. } else {
  41. // keep old state
  42. targetCollapsed = oldCollapsed;
  43. }
  44. if (oldCollapsed !== targetCollapsed) {
  45. element.collapsed = oldCollapsed;
  46. return true;
  47. }
  48. return false;
  49. }
  50. /**
  51. * This module takes care of replacing BPMN elements
  52. */
  53. export default function BpmnReplace(
  54. bpmnFactory, elementFactory, replace,
  55. selection, modeling, eventBus
  56. ) {
  57. var helper = new ModelCloneHelper(eventBus, bpmnFactory);
  58. /**
  59. * Prepares a new business object for the replacement element
  60. * and triggers the replace operation.
  61. *
  62. * @param {djs.model.Base} element
  63. * @param {Object} target
  64. * @param {Object} [hints]
  65. *
  66. * @return {djs.model.Base} the newly created element
  67. */
  68. function replaceElement(element, target, hints) {
  69. hints = hints || {};
  70. var type = target.type,
  71. oldBusinessObject = element.businessObject;
  72. if (isSubProcess(oldBusinessObject)) {
  73. if (type === 'bpmn:SubProcess') {
  74. if (toggeling(element, target)) {
  75. // expanding or collapsing process
  76. modeling.toggleCollapse(element);
  77. return element;
  78. }
  79. }
  80. }
  81. var newBusinessObject = bpmnFactory.create(type);
  82. var newElement = {
  83. type: type,
  84. businessObject: newBusinessObject
  85. };
  86. var elementProps = getProperties(oldBusinessObject.$descriptor),
  87. newElementProps = getProperties(newBusinessObject.$descriptor, true),
  88. copyProps = intersection(elementProps, newElementProps);
  89. // initialize special properties defined in target definition
  90. assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));
  91. var properties = filter(copyProps, function(property) {
  92. var propName = property.replace(/bpmn:/, '');
  93. // copying event definitions, unless we replace
  94. if (propName === 'eventDefinitions') {
  95. return hasEventDefinition(element, target.eventDefinitionType);
  96. }
  97. // retain loop characteristics if the target element
  98. // is not an event sub process
  99. if (propName === 'loopCharacteristics') {
  100. return !isEventSubProcess(newBusinessObject);
  101. }
  102. // so the applied properties from 'target' don't get lost
  103. if (property in newBusinessObject) {
  104. return false;
  105. }
  106. if (propName === 'processRef' && target.isExpanded === false) {
  107. return false;
  108. }
  109. if (propName === 'triggeredByEvent') {
  110. return false;
  111. }
  112. return IGNORED_PROPERTIES.indexOf(propName) === -1;
  113. });
  114. newBusinessObject = helper.clone(oldBusinessObject, newBusinessObject, properties);
  115. // initialize custom BPMN extensions
  116. if (target.eventDefinitionType) {
  117. // only initialize with new eventDefinition
  118. // if we did not set an event definition yet,
  119. // i.e. because we cloned it
  120. if (!hasEventDefinition(newBusinessObject, target.eventDefinitionType)) {
  121. newElement.eventDefinitionType = target.eventDefinitionType;
  122. }
  123. }
  124. if (is(oldBusinessObject, 'bpmn:Activity')) {
  125. if (isSubProcess(oldBusinessObject)) {
  126. // no toggeling, so keep old state
  127. newElement.isExpanded = isExpanded(oldBusinessObject);
  128. }
  129. // else if property is explicitly set, use it
  130. else if (target && has(target, 'isExpanded')) {
  131. newElement.isExpanded = target.isExpanded;
  132. }
  133. // TODO: need also to respect min/max Size
  134. // copy size, from an expanded subprocess to an expanded alternative subprocess
  135. // except bpmn:Task, because Task is always expanded
  136. if ((isExpanded(oldBusinessObject) && !is(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
  137. newElement.width = element.width;
  138. newElement.height = element.height;
  139. }
  140. }
  141. // remove children if not expanding sub process
  142. if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
  143. hints.moveChildren = false;
  144. }
  145. // transform collapsed/expanded pools
  146. if (is(oldBusinessObject, 'bpmn:Participant')) {
  147. // create expanded pool
  148. if (target.isExpanded === true) {
  149. newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
  150. } else {
  151. // remove children when transforming to collapsed pool
  152. hints.moveChildren = false;
  153. }
  154. // apply same width and default height
  155. newElement.width = element.width;
  156. newElement.height = elementFactory._getDefaultSize(newBusinessObject).height;
  157. }
  158. newBusinessObject.name = oldBusinessObject.name;
  159. // retain default flow's reference between inclusive <-> exclusive gateways and activities
  160. if (
  161. isAny(oldBusinessObject, [
  162. 'bpmn:ExclusiveGateway',
  163. 'bpmn:InclusiveGateway',
  164. 'bpmn:Activity'
  165. ]) &&
  166. isAny(newBusinessObject, [
  167. 'bpmn:ExclusiveGateway',
  168. 'bpmn:InclusiveGateway',
  169. 'bpmn:Activity'
  170. ])
  171. ) {
  172. newBusinessObject.default = oldBusinessObject.default;
  173. }
  174. if ('fill' in oldBusinessObject.di || 'stroke' in oldBusinessObject.di) {
  175. assign(newElement, { colors: pick(oldBusinessObject.di, [ 'fill', 'stroke' ]) });
  176. }
  177. newElement = replace.replaceElement(element, newElement, hints);
  178. if (hints.select !== false) {
  179. selection.select(newElement);
  180. }
  181. return newElement;
  182. }
  183. this.replaceElement = replaceElement;
  184. }
  185. BpmnReplace.$inject = [
  186. 'bpmnFactory',
  187. 'elementFactory',
  188. 'replace',
  189. 'selection',
  190. 'modeling',
  191. 'eventBus'
  192. ];
  193. function isSubProcess(bo) {
  194. return is(bo, 'bpmn:SubProcess');
  195. }
  196. function hasEventDefinition(element, type) {
  197. var bo = getBusinessObject(element);
  198. return type && bo.get('eventDefinitions').some(function(definition) {
  199. return is(definition, type);
  200. });
  201. }
  202. /**
  203. * Compute intersection between two arrays.
  204. */
  205. function intersection(a1, a2) {
  206. return a1.filter(function(el) {
  207. return a2.indexOf(el) !== -1;
  208. });
  209. }