bpmn-js/lib/features/popup-menu/ReplaceMenuProvider.js

606 lines
16 KiB
JavaScript

import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
isEventSubProcess,
isExpanded
} from '../../util/DiUtil';
import {
isDifferentType
} from './util/TypeUtil';
import {
forEach,
filter
} from 'min-dash';
import * as replaceOptions from '../replace/ReplaceOptions';
/**
* This module is an element agnostic replace menu provider for the popup menu.
*/
export default function ReplaceMenuProvider(
bpmnFactory, popupMenu, modeling, moddle,
bpmnReplace, rules, translate) {
this._bpmnFactory = bpmnFactory;
this._popupMenu = popupMenu;
this._modeling = modeling;
this._moddle = moddle;
this._bpmnReplace = bpmnReplace;
this._rules = rules;
this._translate = translate;
this.register();
}
ReplaceMenuProvider.$inject = [
'bpmnFactory',
'popupMenu',
'modeling',
'moddle',
'bpmnReplace',
'rules',
'translate'
];
/**
* Register replace menu provider in the popup menu
*/
ReplaceMenuProvider.prototype.register = function() {
this._popupMenu.registerProvider('bpmn-replace', this);
};
/**
* Get all entries from replaceOptions for the given element and apply filters
* on them. Get for example only elements, which are different from the current one.
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu entry items
*/
ReplaceMenuProvider.prototype.getEntries = function(element) {
var businessObject = element.businessObject;
var rules = this._rules;
var entries;
if (!rules.allowed('shape.replace', { element: element })) {
return [];
}
var differentType = isDifferentType(element);
if (is(businessObject, 'bpmn:DataObjectReference')) {
return this._createEntries(element, replaceOptions.DATA_OBJECT_REFERENCE);
}
if (is(businessObject, 'bpmn:DataStoreReference') && !is(element.parent, 'bpmn:Collaboration')) {
return this._createEntries(element, replaceOptions.DATA_STORE_REFERENCE);
}
// start events outside sub processes
if (is(businessObject, 'bpmn:StartEvent') && !is(businessObject.$parent, 'bpmn:SubProcess')) {
entries = filter(replaceOptions.START_EVENT, differentType);
return this._createEntries(element, entries);
}
// expanded/collapsed pools
if (is(businessObject, 'bpmn:Participant')) {
entries = filter(replaceOptions.PARTICIPANT, function(entry) {
return isExpanded(element) !== entry.target.isExpanded;
});
return this._createEntries(element, entries);
}
// start events inside event sub processes
if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
entries = filter(replaceOptions.EVENT_SUB_PROCESS_START_EVENT, function(entry) {
var target = entry.target;
var isInterrupting = target.isInterrupting !== false;
var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;
// filters elements which types and event definition are equal but have have different interrupting types
return differentType(entry) || !differentType(entry) && !isInterruptingEqual;
});
return this._createEntries(element, entries);
}
// start events inside sub processes
if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)
&& is(businessObject.$parent, 'bpmn:SubProcess')) {
entries = filter(replaceOptions.START_EVENT_SUB_PROCESS, differentType);
return this._createEntries(element, entries);
}
// end events
if (is(businessObject, 'bpmn:EndEvent')) {
entries = filter(replaceOptions.END_EVENT, function(entry) {
var target = entry.target;
// hide cancel end events outside transactions
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) {
return false;
}
return differentType(entry);
});
return this._createEntries(element, entries);
}
// boundary events
if (is(businessObject, 'bpmn:BoundaryEvent')) {
entries = filter(replaceOptions.BOUNDARY_EVENT, function(entry) {
var target = entry.target;
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' &&
!is(businessObject.attachedToRef, 'bpmn:Transaction')) {
return false;
}
var cancelActivity = target.cancelActivity !== false;
var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
});
return this._createEntries(element, entries);
}
// intermediate events
if (is(businessObject, 'bpmn:IntermediateCatchEvent') ||
is(businessObject, 'bpmn:IntermediateThrowEvent')) {
entries = filter(replaceOptions.INTERMEDIATE_EVENT, differentType);
return this._createEntries(element, entries);
}
// gateways
if (is(businessObject, 'bpmn:Gateway')) {
entries = filter(replaceOptions.GATEWAY, differentType);
return this._createEntries(element, entries);
}
// transactions
if (is(businessObject, 'bpmn:Transaction')) {
entries = filter(replaceOptions.TRANSACTION, differentType);
return this._createEntries(element, entries);
}
// expanded event sub processes
if (isEventSubProcess(businessObject) && isExpanded(element)) {
entries = filter(replaceOptions.EVENT_SUB_PROCESS, differentType);
return this._createEntries(element, entries);
}
// expanded sub processes
if (is(businessObject, 'bpmn:SubProcess') && isExpanded(element)) {
entries = filter(replaceOptions.SUBPROCESS_EXPANDED, differentType);
return this._createEntries(element, entries);
}
// collapsed ad hoc sub processes
if (is(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(element)) {
entries = filter(replaceOptions.TASK, function(entry) {
var target = entry.target;
var isTargetSubProcess = target.type === 'bpmn:SubProcess';
var isTargetExpanded = target.isExpanded === true;
return isDifferentType(element, target) && (!isTargetSubProcess || isTargetExpanded);
});
return this._createEntries(element, entries);
}
// sequence flows
if (is(businessObject, 'bpmn:SequenceFlow')) {
return this._createSequenceFlowEntries(element, replaceOptions.SEQUENCE_FLOW);
}
// flow nodes
if (is(businessObject, 'bpmn:FlowNode')) {
entries = filter(replaceOptions.TASK, differentType);
// collapsed SubProcess can not be replaced with itself
if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(element)) {
entries = filter(entries, function(entry) {
return entry.label !== 'Sub Process (collapsed)';
});
}
return this._createEntries(element, entries);
}
return [];
};
/**
* Get a list of header items for the given element. This includes buttons
* for multi instance markers and for the ad hoc marker.
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu entry items
*/
ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
var headerEntries = [];
if (is(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
headerEntries = headerEntries.concat(this._getLoopEntries(element));
}
if (is(element, 'bpmn:DataObjectReference')) {
headerEntries = headerEntries.concat(this._getDataObjectIsCollection(element));
}
if (is(element, 'bpmn:Participant')) {
headerEntries = headerEntries.concat(this._getParticipantMultiplicity(element));
}
if (is(element, 'bpmn:SubProcess') &&
!is(element, 'bpmn:Transaction') &&
!isEventSubProcess(element)) {
headerEntries.push(this._getAdHocEntry(element));
}
return headerEntries;
};
/**
* Creates an array of menu entry objects for a given element and filters the replaceOptions
* according to a filter function.
*
* @param {djs.model.Base} element
* @param {Object} replaceOptions
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
var menuEntries = [];
var self = this;
forEach(replaceOptions, function(definition) {
var entry = self._createMenuEntry(definition, element);
menuEntries.push(entry);
});
return menuEntries;
};
/**
* Creates an array of menu entry objects for a given sequence flow.
*
* @param {djs.model.Base} element
* @param {Object} replaceOptions
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {
var businessObject = getBusinessObject(element);
var menuEntries = [];
var modeling = this._modeling,
moddle = this._moddle;
var self = this;
forEach(replaceOptions, function(entry) {
switch (entry.actionName) {
case 'replace-with-default-flow':
if (businessObject.sourceRef.default !== businessObject &&
(is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is(businessObject.sourceRef, 'bpmn:Activity'))) {
menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element.source, { default: businessObject });
}));
}
break;
case 'replace-with-conditional-flow':
if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) {
menuEntries.push(self._createMenuEntry(entry, element, function() {
var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
modeling.updateProperties(element, { conditionExpression: conditionExpression });
}));
}
break;
default:
// default flows
if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
return menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element, { conditionExpression: undefined });
}));
}
// conditional flows
if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
is(businessObject.sourceRef, 'bpmn:Activity')) &&
businessObject.sourceRef.default === businessObject) {
return menuEntries.push(self._createMenuEntry(entry, element, function() {
modeling.updateProperties(element.source, { default: undefined });
}));
}
}
});
return menuEntries;
};
/**
* Creates and returns a single menu entry item.
*
* @param {Object} definition a single replace options definition object
* @param {djs.model.Base} element
* @param {Function} [action] an action callback function which gets called when
* the menu entry is being triggered.
*
* @return {Object} menu entry item
*/
ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
var translate = this._translate;
var replaceElement = this._bpmnReplace.replaceElement;
var replaceAction = function() {
return replaceElement(element, definition.target);
};
var label = definition.label;
if (label && typeof label === 'function') {
label = label(element);
}
action = action || replaceAction;
var menuEntry = {
label: translate(label),
className: definition.className,
id: definition.actionName,
action: action
};
return menuEntry;
};
/**
* Get a list of menu items containing buttons for multi instance markers
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getLoopEntries = function(element) {
var self = this;
var translate = this._translate;
function toggleLoopEntry(event, entry) {
var loopCharacteristics;
if (entry.active) {
loopCharacteristics = undefined;
} else {
loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
if (entry.options.isSequential) {
loopCharacteristics.isSequential = entry.options.isSequential;
}
}
self._modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics });
}
var businessObject = getBusinessObject(element),
loopCharacteristics = businessObject.loopCharacteristics;
var isSequential,
isLoop,
isParallel;
if (loopCharacteristics) {
isSequential = loopCharacteristics.isSequential;
isLoop = loopCharacteristics.isSequential === undefined;
isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
}
var loopEntries = [
{
id: 'toggle-parallel-mi',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Parallel Multi Instance'),
active: isParallel,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: false
}
},
{
id: 'toggle-sequential-mi',
className: 'bpmn-icon-sequential-mi-marker',
title: translate('Sequential Multi Instance'),
active: isSequential,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
isSequential: true
}
},
{
id: 'toggle-loop',
className: 'bpmn-icon-loop-marker',
title: translate('Loop'),
active: isLoop,
action: toggleLoopEntry,
options: {
loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
}
}
];
return loopEntries;
};
/**
* Get a list of menu items containing a button for the collection marker
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getDataObjectIsCollection = function(element) {
var self = this;
var translate = this._translate;
function toggleIsCollection(event, entry) {
self._modeling.updateModdleProperties(
element,
dataObject,
{ isCollection: !entry.active });
}
var dataObject = element.businessObject.dataObjectRef,
isCollection = dataObject.isCollection;
var dataObjectEntries = [
{
id: 'toggle-is-collection',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Collection'),
active: isCollection,
action: toggleIsCollection,
}
];
return dataObjectEntries;
};
/**
* Get a list of menu items containing a button for the participant multiplicity marker
*
* @param {djs.model.Base} element
*
* @return {Array<Object>} a list of menu items
*/
ReplaceMenuProvider.prototype._getParticipantMultiplicity = function(element) {
var self = this;
var bpmnFactory = this._bpmnFactory;
var translate = this._translate;
function toggleParticipantMultiplicity(event, entry) {
var isActive = entry.active;
var participantMultiplicity;
if (!isActive) {
participantMultiplicity = bpmnFactory.create('bpmn:ParticipantMultiplicity');
}
self._modeling.updateProperties(
element,
{ participantMultiplicity: participantMultiplicity });
}
var participantMultiplicity = element.businessObject.participantMultiplicity;
var participantEntries = [
{
id: 'toggle-participant-multiplicity',
className: 'bpmn-icon-parallel-mi-marker',
title: translate('Participant Multiplicity'),
active: !!participantMultiplicity,
action: toggleParticipantMultiplicity,
}
];
return participantEntries;
};
/**
* Get the menu items containing a button for the ad hoc marker
*
* @param {djs.model.Base} element
*
* @return {Object} a menu item
*/
ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
var translate = this._translate;
var businessObject = getBusinessObject(element);
var isAdHoc = is(businessObject, 'bpmn:AdHocSubProcess');
var replaceElement = this._bpmnReplace.replaceElement;
var adHocEntry = {
id: 'toggle-adhoc',
className: 'bpmn-icon-ad-hoc-marker',
title: translate('Ad-hoc'),
active: isAdHoc,
action: function(event, entry) {
if (isAdHoc) {
return replaceElement(element, { type: 'bpmn:SubProcess' }, {
autoResize: false,
layoutConnection: false
});
} else {
return replaceElement(element, { type: 'bpmn:AdHocSubProcess' }, {
autoResize: false,
layoutConnection: false
});
}
}
};
return adHocEntry;
};