ReplaceMenuProvider.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import {
  2. getBusinessObject,
  3. is
  4. } from '../../util/ModelUtil';
  5. import {
  6. isEventSubProcess,
  7. isExpanded
  8. } from '../../util/DiUtil';
  9. import {
  10. isDifferentType
  11. } from './util/TypeUtil';
  12. import {
  13. forEach,
  14. filter
  15. } from 'min-dash';
  16. import * as replaceOptions from '../replace/ReplaceOptions';
  17. /**
  18. * This module is an element agnostic replace menu provider for the popup menu.
  19. */
  20. export default function ReplaceMenuProvider(
  21. popupMenu, modeling, moddle,
  22. bpmnReplace, rules, translate) {
  23. this._popupMenu = popupMenu;
  24. this._modeling = modeling;
  25. this._moddle = moddle;
  26. this._bpmnReplace = bpmnReplace;
  27. this._rules = rules;
  28. this._translate = translate;
  29. this.register();
  30. }
  31. ReplaceMenuProvider.$inject = [
  32. 'popupMenu',
  33. 'modeling',
  34. 'moddle',
  35. 'bpmnReplace',
  36. 'rules',
  37. 'translate'
  38. ];
  39. /**
  40. * Register replace menu provider in the popup menu
  41. */
  42. ReplaceMenuProvider.prototype.register = function() {
  43. this._popupMenu.registerProvider('bpmn-replace', this);
  44. };
  45. /**
  46. * Get all entries from replaceOptions for the given element and apply filters
  47. * on them. Get for example only elements, which are different from the current one.
  48. *
  49. * @param {djs.model.Base} element
  50. *
  51. * @return {Array<Object>} a list of menu entry items
  52. */
  53. ReplaceMenuProvider.prototype.getEntries = function(element) {
  54. var businessObject = element.businessObject;
  55. var rules = this._rules;
  56. var entries;
  57. if (!rules.allowed('shape.replace', { element: element })) {
  58. return [];
  59. }
  60. var differentType = isDifferentType(element);
  61. // start events outside event sub processes
  62. if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)) {
  63. entries = filter(replaceOptions.START_EVENT, differentType);
  64. return this._createEntries(element, entries);
  65. }
  66. // expanded/collapsed pools
  67. if (is(businessObject, 'bpmn:Participant')) {
  68. entries = filter(replaceOptions.PARTICIPANT, function(entry) {
  69. return isExpanded(businessObject) !== entry.target.isExpanded;
  70. });
  71. return this._createEntries(element, entries);
  72. }
  73. // start events inside event sub processes
  74. if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
  75. entries = filter(replaceOptions.EVENT_SUB_PROCESS_START_EVENT, function(entry) {
  76. var target = entry.target;
  77. var isInterrupting = target.isInterrupting !== false;
  78. var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;
  79. // filters elements which types and event definition are equal but have have different interrupting types
  80. return differentType(entry) || !differentType(entry) && !isInterruptingEqual;
  81. });
  82. return this._createEntries(element, entries);
  83. }
  84. // end events
  85. if (is(businessObject, 'bpmn:EndEvent')) {
  86. entries = filter(replaceOptions.END_EVENT, function(entry) {
  87. var target = entry.target;
  88. // hide cancel end events outside transactions
  89. if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) {
  90. return false;
  91. }
  92. return differentType(entry);
  93. });
  94. return this._createEntries(element, entries);
  95. }
  96. // boundary events
  97. if (is(businessObject, 'bpmn:BoundaryEvent')) {
  98. entries = filter(replaceOptions.BOUNDARY_EVENT, function(entry) {
  99. var target = entry.target;
  100. if (target.eventDefinition == 'bpmn:CancelEventDefinition' &&
  101. !is(businessObject.attachedToRef, 'bpmn:Transaction')) {
  102. return false;
  103. }
  104. var cancelActivity = target.cancelActivity !== false;
  105. var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
  106. return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
  107. });
  108. return this._createEntries(element, entries);
  109. }
  110. // intermediate events
  111. if (is(businessObject, 'bpmn:IntermediateCatchEvent') ||
  112. is(businessObject, 'bpmn:IntermediateThrowEvent')) {
  113. entries = filter(replaceOptions.INTERMEDIATE_EVENT, differentType);
  114. return this._createEntries(element, entries);
  115. }
  116. // gateways
  117. if (is(businessObject, 'bpmn:Gateway')) {
  118. entries = filter(replaceOptions.GATEWAY, differentType);
  119. return this._createEntries(element, entries);
  120. }
  121. // transactions
  122. if (is(businessObject, 'bpmn:Transaction')) {
  123. entries = filter(replaceOptions.TRANSACTION, differentType);
  124. return this._createEntries(element, entries);
  125. }
  126. // expanded event sub processes
  127. if (isEventSubProcess(businessObject) && isExpanded(businessObject)) {
  128. entries = filter(replaceOptions.EVENT_SUB_PROCESS, differentType);
  129. return this._createEntries(element, entries);
  130. }
  131. // expanded sub processes
  132. if (is(businessObject, 'bpmn:SubProcess') && isExpanded(businessObject)) {
  133. entries = filter(replaceOptions.SUBPROCESS_EXPANDED, differentType);
  134. return this._createEntries(element, entries);
  135. }
  136. // collapsed ad hoc sub processes
  137. if (is(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(businessObject)) {
  138. entries = filter(replaceOptions.TASK, function(entry) {
  139. var target = entry.target;
  140. var isTargetSubProcess = target.type === 'bpmn:SubProcess';
  141. var isTargetExpanded = target.isExpanded === true;
  142. return isDifferentType(element, target) && (!isTargetSubProcess || isTargetExpanded);
  143. });
  144. return this._createEntries(element, entries);
  145. }
  146. // sequence flows
  147. if (is(businessObject, 'bpmn:SequenceFlow')) {
  148. return this._createSequenceFlowEntries(element, replaceOptions.SEQUENCE_FLOW);
  149. }
  150. // flow nodes
  151. if (is(businessObject, 'bpmn:FlowNode')) {
  152. entries = filter(replaceOptions.TASK, differentType);
  153. // collapsed SubProcess can not be replaced with itself
  154. if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(businessObject)) {
  155. entries = filter(entries, function(entry) {
  156. return entry.label !== 'Sub Process (collapsed)';
  157. });
  158. }
  159. return this._createEntries(element, entries);
  160. }
  161. return [];
  162. };
  163. /**
  164. * Get a list of header items for the given element. This includes buttons
  165. * for multi instance markers and for the ad hoc marker.
  166. *
  167. * @param {djs.model.Base} element
  168. *
  169. * @return {Array<Object>} a list of menu entry items
  170. */
  171. ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
  172. var headerEntries = [];
  173. if (is(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
  174. headerEntries = headerEntries.concat(this._getLoopEntries(element));
  175. }
  176. if (is(element, 'bpmn:SubProcess') &&
  177. !is(element, 'bpmn:Transaction') &&
  178. !isEventSubProcess(element)) {
  179. headerEntries.push(this._getAdHocEntry(element));
  180. }
  181. return headerEntries;
  182. };
  183. /**
  184. * Creates an array of menu entry objects for a given element and filters the replaceOptions
  185. * according to a filter function.
  186. *
  187. * @param {djs.model.Base} element
  188. * @param {Object} replaceOptions
  189. *
  190. * @return {Array<Object>} a list of menu items
  191. */
  192. ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
  193. var menuEntries = [];
  194. var self = this;
  195. forEach(replaceOptions, function(definition) {
  196. var entry = self._createMenuEntry(definition, element);
  197. menuEntries.push(entry);
  198. });
  199. return menuEntries;
  200. };
  201. /**
  202. * Creates an array of menu entry objects for a given sequence flow.
  203. *
  204. * @param {djs.model.Base} element
  205. * @param {Object} replaceOptions
  206. * @return {Array<Object>} a list of menu items
  207. */
  208. ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {
  209. var businessObject = getBusinessObject(element);
  210. var menuEntries = [];
  211. var modeling = this._modeling,
  212. moddle = this._moddle;
  213. var self = this;
  214. forEach(replaceOptions, function(entry) {
  215. switch (entry.actionName) {
  216. case 'replace-with-default-flow':
  217. if (businessObject.sourceRef.default !== businessObject &&
  218. (is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
  219. is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
  220. is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
  221. is(businessObject.sourceRef, 'bpmn:Activity'))) {
  222. menuEntries.push(self._createMenuEntry(entry, element, function() {
  223. modeling.updateProperties(element.source, { default: businessObject });
  224. }));
  225. }
  226. break;
  227. case 'replace-with-conditional-flow':
  228. if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) {
  229. menuEntries.push(self._createMenuEntry(entry, element, function() {
  230. var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
  231. modeling.updateProperties(element, { conditionExpression: conditionExpression });
  232. }));
  233. }
  234. break;
  235. default:
  236. // default flows
  237. if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
  238. return menuEntries.push(self._createMenuEntry(entry, element, function() {
  239. modeling.updateProperties(element, { conditionExpression: undefined });
  240. }));
  241. }
  242. // conditional flows
  243. if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
  244. is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
  245. is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
  246. is(businessObject.sourceRef, 'bpmn:Activity')) &&
  247. businessObject.sourceRef.default === businessObject) {
  248. return menuEntries.push(self._createMenuEntry(entry, element, function() {
  249. modeling.updateProperties(element.source, { default: undefined });
  250. }));
  251. }
  252. }
  253. });
  254. return menuEntries;
  255. };
  256. /**
  257. * Creates and returns a single menu entry item.
  258. *
  259. * @param {Object} definition a single replace options definition object
  260. * @param {djs.model.Base} element
  261. * @param {Function} [action] an action callback function which gets called when
  262. * the menu entry is being triggered.
  263. *
  264. * @return {Object} menu entry item
  265. */
  266. ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
  267. var translate = this._translate;
  268. var replaceElement = this._bpmnReplace.replaceElement;
  269. var replaceAction = function() {
  270. return replaceElement(element, definition.target);
  271. };
  272. action = action || replaceAction;
  273. var menuEntry = {
  274. label: translate(definition.label),
  275. className: definition.className,
  276. id: definition.actionName,
  277. action: action
  278. };
  279. return menuEntry;
  280. };
  281. /**
  282. * Get a list of menu items containing buttons for multi instance markers
  283. *
  284. * @param {djs.model.Base} element
  285. *
  286. * @return {Array<Object>} a list of menu items
  287. */
  288. ReplaceMenuProvider.prototype._getLoopEntries = function(element) {
  289. var self = this;
  290. var translate = this._translate;
  291. function toggleLoopEntry(event, entry) {
  292. var loopCharacteristics;
  293. if (entry.active) {
  294. loopCharacteristics = undefined;
  295. } else {
  296. loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
  297. if (entry.options.isSequential) {
  298. loopCharacteristics.isSequential = entry.options.isSequential;
  299. }
  300. }
  301. self._modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics });
  302. }
  303. var businessObject = getBusinessObject(element),
  304. loopCharacteristics = businessObject.loopCharacteristics;
  305. var isSequential,
  306. isLoop,
  307. isParallel;
  308. if (loopCharacteristics) {
  309. isSequential = loopCharacteristics.isSequential;
  310. isLoop = loopCharacteristics.isSequential === undefined;
  311. isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
  312. }
  313. var loopEntries = [
  314. {
  315. id: 'toggle-parallel-mi',
  316. className: 'bpmn-icon-parallel-mi-marker',
  317. title: translate('Parallel Multi Instance'),
  318. active: isParallel,
  319. action: toggleLoopEntry,
  320. options: {
  321. loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
  322. isSequential: false
  323. }
  324. },
  325. {
  326. id: 'toggle-sequential-mi',
  327. className: 'bpmn-icon-sequential-mi-marker',
  328. title: translate('Sequential Multi Instance'),
  329. active: isSequential,
  330. action: toggleLoopEntry,
  331. options: {
  332. loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
  333. isSequential: true
  334. }
  335. },
  336. {
  337. id: 'toggle-loop',
  338. className: 'bpmn-icon-loop-marker',
  339. title: translate('Loop'),
  340. active: isLoop,
  341. action: toggleLoopEntry,
  342. options: {
  343. loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
  344. }
  345. }
  346. ];
  347. return loopEntries;
  348. };
  349. /**
  350. * Get the menu items containing a button for the ad hoc marker
  351. *
  352. * @param {djs.model.Base} element
  353. *
  354. * @return {Object} a menu item
  355. */
  356. ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
  357. var translate = this._translate;
  358. var businessObject = getBusinessObject(element);
  359. var isAdHoc = is(businessObject, 'bpmn:AdHocSubProcess');
  360. var replaceElement = this._bpmnReplace.replaceElement;
  361. var adHocEntry = {
  362. id: 'toggle-adhoc',
  363. className: 'bpmn-icon-ad-hoc-marker',
  364. title: translate('Ad-hoc'),
  365. active: isAdHoc,
  366. action: function(event, entry) {
  367. if (isAdHoc) {
  368. return replaceElement(element, { type: 'bpmn:SubProcess' }, {
  369. autoResize: false,
  370. layoutConnection: false
  371. });
  372. } else {
  373. return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, {
  374. autoResize: false,
  375. layoutConnection: false
  376. });
  377. }
  378. }
  379. };
  380. return adHocEntry;
  381. };