cmmn-js
Version:
A cmmn 1.1 toolkit
449 lines (315 loc) • 10.5 kB
JavaScript
'use strict';
var forEach = require('min-dash').forEach,
filter = require('min-dash').filter;
var replaceOptions = require('../replace/ReplaceOptions');
var isItemCapable = require('../modeling/util/PlanItemDefinitionUtil').isItemCapable;
var isAny = require('../modeling/util/ModelingUtil').isAny;
var ModelUtil = require('../../util/ModelUtil'),
getBusinessObject = ModelUtil.getBusinessObject,
getDefinition = ModelUtil.getDefinition,
isCasePlanModel = ModelUtil.isCasePlanModel,
isRequired = ModelUtil.isRequired,
isRepeatable = ModelUtil.isRepeatable,
isManualActivation = ModelUtil.isManualActivation,
is = ModelUtil.is;
var isCollapsed = require('../../util/DiUtil').isCollapsed;
/**
* Returns true, if an element is from a different type.
* discretionary / non discretionary
*
* @param {djs.model.Base} element
* @param {Object} entry
*
* @return {Boolean}
*/
function isDifferentType(element, entry) {
var businessObject = getBusinessObject(element);
return businessObject.$type !== entry.target.type;
}
/**
* Returns true if the definition type is different.
* e.g. process task / human task
*
* @param {djs.model.Base} element
* @param {Object} entry
*
* @return {Boolean}
*/
function isDifferentDefinitionType(element, entry) {
return getDefinition(element).$type !== entry.target.definitionType;
}
function canReplaceElementByTarget(element, target) {
var type = target.type,
definitionType = target.definitionType,
parent = element.parent,
definition = getDefinition(element);
if (isPlanItem(element)) {
if (type === 'cmmn:DiscretionaryItem' && !isStage(parent)) {
return false;
}
}
if (definitionType === 'cmmn:HumanTask') {
if (isPlanFragment(element) && !target.isBlocking) {
return false;
}
if (isTask(element) && definition.isBlocking !== target.isBlocking) {
return false;
}
}
return true;
}
/**
* This module is an element agnostic replace menu provider for the popup menu.
*/
function ReplaceMenuProvider(popupMenu, cmmnReplace, cmmnFactory, modeling, rules) {
this._popupMenu = popupMenu;
this._cmmnReplace = cmmnReplace;
this._cmmnFactory = cmmnFactory;
this._modeling = modeling;
this._rules = rules;
this.register();
}
ReplaceMenuProvider.$inject = [ 'popupMenu', 'cmmnReplace', 'cmmnFactory', 'modeling', 'rules' ];
/**
* Register replace menu provider in the popup menu
*/
ReplaceMenuProvider.prototype.register = function() {
this._popupMenu.registerProvider('cmmn-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 rules = this._rules;
var entries,
definition = getDefinition(element);
if (!rules.allowed('shape.replace', { element: element })) {
return [];
}
if (isItemCapable(element)) {
if (isTask(element)) {
entries = filter(replaceOptions.TASK, function(entry) {
var target = entry.target;
if (canReplaceElementByTarget(element, target)) {
// ^ === XOR
// returns true if and only of EITHER the type OR the definition type is different
return isDifferentType(element, entry) ^ isDifferentDefinitionType(element, entry);
}
});
return this._createEntries(element, entries);
}
if (isPlanFragment(element)) {
if (!isCollapsed(element)) {
entries = filter(replaceOptions.PLAN_FRAGMENT_EXPANDED, function(entry) {
var target = entry.target;
if (canReplaceElementByTarget(element, target)) {
// returns true if the type or the definition type is different (or both)
return isDifferentType(element, entry) || isDifferentDefinitionType(element, entry);
}
});
return this._createEntries(element, entries);
}
if (isCollapsed(element)) {
entries = filter(replaceOptions.TASK, function(entry) {
var target = entry.target,
definitionType = target.definitionType;
if (!canReplaceElementByTarget(element, target)) {
return false;
}
if (definitionType === definition.$type && !target.isCollapsed) {
return true;
}
// ^ === XOR
// returns true if and only of EITHER the type OR the definition type is different
return isDifferentType(element, entry) ^ isDifferentDefinitionType(element, entry);
});
return this._createEntries(element, entries);
}
}
if (isEventListener(element)) {
entries = filter(replaceOptions.EVENT_LISTENER, function(entry) {
return isDifferentDefinitionType(element, entry);
});
return this._createEntries(element, entries);
}
}
if (isCriterion(element)) {
entries = filter(replaceOptions.CRITERION, function(entry) {
return isDifferentType(element, entry);
});
return this._createEntries(element, entries);
}
return [];
};
ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
var headerEntries = [];
if (isTask(element)) {
headerEntries = headerEntries.concat(this._getBlockingEntry(element));
}
if (isStage(element)) {
headerEntries = headerEntries.concat(this._getAutoCompleteEntry(element));
}
if (ensureSupportRules(element)) {
headerEntries = headerEntries.concat(this._getRuleEntries(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 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 replaceElement = this._cmmnReplace.replaceElement;
var replaceAction = function() {
return replaceElement(element, definition.target);
};
action = action || replaceAction;
var menuEntry = {
label: definition.label,
className: definition.className,
id: definition.actionName,
action: action
};
return menuEntry;
};
ReplaceMenuProvider.prototype._getBlockingEntry = function(element) {
var self = this;
var definition = getDefinition(element);
function toggleBlockingEntry(event, entry) {
var blocking = !entry.active;
self._modeling.updateControls(element, { isBlocking: blocking });
}
var isBlocking = definition.isBlocking;
var entries = [
{
id: 'toggle-is-blocking',
className: 'cmmn-icon-blocking',
title: 'Blocking',
active: isBlocking,
action: toggleBlockingEntry
}
];
return entries;
};
ReplaceMenuProvider.prototype._getAutoCompleteEntry = function(element) {
var self = this;
var definition = getDefinition(element);
function toggleAutoCompleteEntry(event, entry) {
var autoComplete = !entry.active;
self._modeling.updateControls(element, { autoComplete: autoComplete });
}
var isAutoComplete = definition.autoComplete;
var entries = [
{
id: 'toggle-auto-complete',
className: 'cmmn-icon-auto-complete-marker',
title: 'Auto Complete',
active: isAutoComplete,
action: toggleAutoCompleteEntry
}
];
return entries;
};
ReplaceMenuProvider.prototype._getRuleEntries = function(element) {
var self = this;
function toggleControlEntry(control, type) {
return function(event, entry) {
var value = {};
if (entry.active) {
value[control] = undefined;
}
else {
value[control] = self._cmmnFactory.create(type);
}
self._modeling.updateControls(element, value);
};
}
var repeatable = isRepeatable(element),
required = isRequired(element),
manualActivation = isManualActivation(element);
var entries = [
{
id: 'toggle-required-rule',
className: 'cmmn-icon-required-marker',
title: 'Required Rule',
active: required,
action: toggleControlEntry('requiredRule', 'cmmn:RequiredRule')
}
];
if (!isMilestone(element)) {
entries.push({
id: 'toggle-manual-activation-rule',
className: 'cmmn-icon-manual-activation-marker',
title: 'Manual Activation Rule',
active: manualActivation,
action: toggleControlEntry('manualActivationRule', 'cmmn:ManualActivationRule')
});
}
entries.push({
id: 'toggle-repetition-rule',
className: 'cmmn-icon-repetition-marker',
title: 'Repetition Rule',
active: repeatable,
action: toggleControlEntry('repetitionRule', 'cmmn:RepetitionRule')
});
return entries;
};
module.exports = ReplaceMenuProvider;
function ensureSupportRules(element) {
var definition = getDefinition(element);
return !isCasePlanModel(element) && isAny(definition, [
'cmmn:Stage',
'cmmn:Milestone',
'cmmn:Task'
]);
}
function isPlanFragment(element) {
return is(getDefinition(element), 'cmmn:PlanFragment');
}
function isStage(element) {
return is(getDefinition(element), 'cmmn:Stage');
}
function isEventListener(element) {
return is(getDefinition(element), 'cmmn:EventListener');
}
function isMilestone(element) {
return is(getDefinition(element), 'cmmn:Milestone');
}
function isTask(element) {
return is(getDefinition(element), 'cmmn:Task');
}
function isCriterion(element) {
return is(element, 'cmmn:Criterion');
}
function isPlanItem(element) {
return is(element, 'cmmn:PlanItem');
}