bpmn-js-element-templates
Version:
Element templates for bpmn-js
7,956 lines • 214 kB
JavaScript
'use strict';
var ModelUtil = require('bpmn-js/lib/util/ModelUtil');
var uuid = require('uuid');
var minDash = require('min-dash');
var semver = require('semver');
var semverCompare = require('semver-compare');
var elementTemplatesValidator = require('@bpmn-io/element-templates-validator');
var Ids = require('ids');
var LabelUtil = require('bpmn-js/lib/features/label-editing/LabelUtil');
var DrilldownUtil = require('bpmn-js/lib/util/DrilldownUtil');
var DiUtil = require('bpmn-js/lib/util/DiUtil');
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
/**
* Check if the property is cast to FEEL expression:
* - Boolean and Number properties with feel set to 'optional' or 'static'
* - Boolean and Number input/output parameters have default feel=static
*
* @returns {boolean}
*/
const shouldCastToFeel = (property) => {
const feel = getFeelValue(property);
return [ 'optional', 'static' ].includes(feel) && [ 'Boolean', 'Number' ].includes(property.type);
};
const ALWAYS_CAST_TO_FEEL = [
'zeebe:input',
'zeebe:output'
];
function getFeelValue(property) {
if (ALWAYS_CAST_TO_FEEL.includes(property.binding.type)) {
return property.feel || 'static';
}
return property.feel;
}
const toFeelExpression = (value, type) => {
if (typeof value === 'string' && value.startsWith('=')) {
return value;
}
if (type === 'Boolean') {
value = value === 'false' ? false : value;
return '=' + !!value;
}
if (typeof value === 'undefined') {
return value;
}
return '=' + value.toString();
};
/**
* The BPMN 2.0 extension attribute name under
* which the element template ID is stored.
*
* @type {String}
*/
const TEMPLATE_ID_ATTR$1 = 'zeebe:modelerTemplate';
/**
* The BPMN 2.0 extension attribute name under
* which the element template version is stored.
*
* @type {String}
*/
const TEMPLATE_VERSION_ATTR$1 = 'zeebe:modelerTemplateVersion';
/**
* The BPMN 2.0 extension attribute name under
* which the element template icon is stored.
*
* @type {String}
*/
const TEMPLATE_ICON_ATTR = 'zeebe:modelerTemplateIcon';
/**
* Get template id for a given diagram element.
*
* @param {djs.model.Base} element
*
* @return {String}
*/
function getTemplateId$1(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (businessObject) {
return businessObject.get(TEMPLATE_ID_ATTR$1);
}
}
/**
* Get template version for a given diagram element.
*
* @param {djs.model.Base} element
*
* @return {String}
*/
function getTemplateVersion$1(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (businessObject) {
return businessObject.get(TEMPLATE_VERSION_ATTR$1);
}
}
/**
* Get template icon for a given diagram element.
*
* @param {djs.model.Base} element
*
* @return {String}
*/
function getTemplateIcon(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (businessObject) {
return businessObject.get(TEMPLATE_ICON_ATTR);
}
}
/**
* Find extension with given type in
* BPMN element, diagram element or ExtensionElement.
*
* @param {ModdleElement|djs.model.Base} element
* @param {String} type
*
* @return {ModdleElement} the extension
*/
function findExtension$1(element, type) {
const businessObject = ModelUtil.getBusinessObject(element);
let extensionElements;
if (ModelUtil.is(businessObject, 'bpmn:ExtensionElements')) {
extensionElements = businessObject;
} else {
extensionElements = businessObject.get('extensionElements');
}
if (!extensionElements) {
return;
}
return extensionElements.get('values').find((value) => {
return ModelUtil.is(value, type);
});
}
function findZeebeProperty(zeebeProperties, binding) {
return zeebeProperties.get('properties').find((value) => {
return value.name === binding.name;
});
}
function findInputParameter(ioMapping, binding) {
const parameters = ioMapping.get('inputParameters');
return parameters.find((parameter) => {
return parameter.target === binding.name;
});
}
function findOutputParameter(ioMapping, binding) {
const parameters = ioMapping.get('outputParameters');
return parameters.find((parameter) => {
return parameter.source === binding.source;
});
}
function findTaskHeader(taskHeaders, binding) {
const headers = taskHeaders.get('values');
return headers.find((header) => {
return header.key === binding.key;
});
}
function findMessage(businessObject) {
if (ModelUtil.is(businessObject, 'bpmn:Event')) {
const eventDefinitions = businessObject.get('eventDefinitions');
if (!eventDefinitions || !eventDefinitions.length) {
return;
}
businessObject = eventDefinitions[0];
}
if (!businessObject) {
return;
}
return businessObject.get('messageRef');
}
function getDefaultValue(property) {
if (
shouldCastToFeel(property) || property.feel === 'required'
) {
return toFeelExpression(property.value, property.type);
}
if (property.value !== undefined) {
return property.value;
}
if (property.generatedValue) {
const { type } = property.generatedValue;
if (type === 'uuid') {
return uuid.v4();
}
}
}
/**
* The BPMN 2.0 extension attribute name under
* which the element template ID is stored.
*
* @type {String}
*/
const TEMPLATE_ID_ATTR = 'camunda:modelerTemplate';
/**
* The BPMN 2.0 extension attribute name under
* which the element template version is stored.
*
* @type {String}
*/
const TEMPLATE_VERSION_ATTR = 'camunda:modelerTemplateVersion';
/**
* Get template id for a given diagram element.
*
* @param {djs.model.Base} element
*
* @return {String}
*/
function getTemplateId(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (businessObject) {
return businessObject.get(TEMPLATE_ID_ATTR);
}
}
/**
* Get template version for a given diagram element.
*
* @param {djs.model.Base} element
*
* @return {String}
*/
function getTemplateVersion(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (businessObject) {
return businessObject.get(TEMPLATE_VERSION_ATTR);
}
}
/**
* Find extension with given type in
* BPMN element, diagram element or ExtensionElement.
*
* @param {ModdleElement|djs.model.Base} element
* @param {String} type
*
* @return {ModdleElement} the extension
*/
function findExtension(element, type) {
const businessObject = ModelUtil.getBusinessObject(element);
let extensionElements;
if (ModelUtil.is(businessObject, 'bpmn:ExtensionElements')) {
extensionElements = businessObject;
} else {
extensionElements = businessObject.get('extensionElements');
}
if (!extensionElements) {
return null;
}
return extensionElements.get('values').find((value) => {
return ModelUtil.is(value, type);
});
}
function findExtensions(element, types) {
const extensionElements = getExtensionElements(element);
if (!extensionElements) {
return [];
}
return extensionElements.get('values').filter((value) => {
return ModelUtil.isAny(value, types);
});
}
function findCamundaErrorEventDefinition(element, errorRef) {
const errorEventDefinitions = findExtensions(element, [ 'camunda:ErrorEventDefinition' ]);
let error;
// error ID has to start with <Error_${ errorRef }_>
return errorEventDefinitions.find((definition) => {
error = definition.get('bpmn:errorRef');
if (error) {
return error.get('bpmn:id').startsWith(`Error_${ errorRef }`);
}
});
}
// helpers //////////
function getExtensionElements(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(businessObject, 'bpmn:ExtensionElements')) {
return businessObject;
} else {
return businessObject.get('extensionElements');
}
}
// eslint-disable-next-line no-undef
const packageVersion = "2.9.1";
/**
* Registry for element templates.
*/
let ElementTemplates$1 = class ElementTemplates {
constructor(commandStack, eventBus, modeling, injector, config) {
this._commandStack = commandStack;
this._eventBus = eventBus;
this._injector = injector;
this._modeling = modeling;
this._templatesById = {};
this._templates = [];
config = config || {};
this._engines = this._coerceEngines(config.engines || {});
eventBus.on('elementTemplates.engines.changed', event => {
this.set(this._templates);
});
}
/**
* Get template with given ID and optional version or for element.
*
* @param {String|djs.model.Base} id
* @param {number} [version]
*
* @return {ElementTemplate}
*/
get(id, version) {
const templates = this._templatesById;
let element;
if (minDash.isUndefined(id)) {
return null;
} else if (minDash.isString(id)) {
if (minDash.isUndefined(version)) {
version = '_';
}
if (templates[ id ] && templates[ id ][ version ]) {
return templates[ id ][ version ];
} else {
return null;
}
} else {
element = id;
return this.get(this._getTemplateId(element), this._getTemplateVersion(element));
}
}
/**
* Get default template for given element.
*
* @param {djs.model.Base} element
*
* @return {ElementTemplate}
*/
getDefault(element) {
return minDash.find(this.getAll(element), function(template) {
return template.isDefault;
}) || null;
}
/**
* Get all templates (with given ID or applicable to element).
*
* @param {string|djs.model.Base} [id]
* @return {Array<ElementTemplate>}
*/
getAll(id) {
return this._getTemplateVerions(id, { includeDeprecated: true });
}
/**
* Get all templates (with given ID or applicable to element) with the latest
* version.
*
* @param {String|djs.model.Base} [id]
* @param {{ deprecated?: boolean }} [options]
*
* @return {Array<ElementTemplate>}
*/
getLatest(id, options = {}) {
return this._getTemplateVerions(id, {
...options,
latest: true
});
}
/**
* Set templates.
*
* @param {Array<ElementTemplate>} templates
*/
set(templates) {
this._templatesById = {};
this._templates = templates;
templates.forEach((template) => {
const id = template.id;
const version = minDash.isUndefined(template.version) ? '_' : template.version;
if (!this._templatesById[ id ]) {
this._templatesById[ id ] = { };
}
this._templatesById[ id ][ version ] = template;
const latest = this._templatesById[ id ].latest;
if (this.isCompatible(template)) {
if (!latest || minDash.isUndefined(latest.version) || latest.version < version) {
this._templatesById[ id ].latest = template;
}
}
});
this._fire('changed');
}
getEngines() {
return this._engines;
}
setEngines(engines) {
this._engines = this._coerceEngines(engines);
this._fire('engines.changed');
}
/**
* Ensures that only valid engines are kept around
*
* @param { Record<string, string> } engines
*
* @return { Record<string, string> } filtered, valid engines
*/
_coerceEngines(engines) {
// we provide <elementTemplates> engine with the current
// package version; templates may use that engine to declare
// compatibility with this library
engines = {
elementTemplates: packageVersion,
...engines
};
return minDash.reduce(engines, (validEngines, version, engine) => {
const coercedVersion = semver.coerce(version);
if (!semver.valid(coercedVersion)) {
console.error(
new Error(`Engine <${ engine }> specifies unparseable version <${version}>`)
);
return validEngines;
}
return {
...validEngines,
[ engine ]: coercedVersion.raw
};
}, {});
}
/**
* Check if template is compatible with currently set engine version.
*
* @param {ElementTemplate} template
*
* @return {boolean} - true if compatible or no engine is set for elementTemplates or template.
*/
isCompatible(template) {
return !Object.keys(this.getIncompatibleEngines(template)).length;
}
/**
* Get engines that are incompatible with the template.
*
* @param {any} template
*
* @return { Record<string, { required: string, found: string } } - incompatible engines along with their template and local versions
*/
getIncompatibleEngines(template) {
const localEngines = this._engines;
const templateEngines = template.engines;
return minDash.reduce(templateEngines, (result, _, engine) => {
if (!minDash.has(localEngines, engine)) {
return result;
}
if (!semver.satisfies(localEngines[engine], templateEngines[engine])) {
result[engine] = {
actual: localEngines[engine],
required: templateEngines[engine]
};
}
return result;
}, {});
}
/**
* @param {object|string|null} id
* @param { { latest?: boolean, deprecated?: boolean } [options]
*
* @return {Array<ElementTemplate>}
*/
_getTemplateVerions(id, options = {}) {
const {
latest: includeLatestOnly,
deprecated: includeDeprecated
} = options;
const templatesById = this._templatesById;
const getVersions = (template) => {
const { latest, ...versions } = template;
return includeLatestOnly ? (
!includeDeprecated && (latest && latest.deprecated) ? [] : (latest ? [ latest ] : [])
) : minDash.values(versions) ;
};
if (minDash.isNil(id)) {
return minDash.flatten(minDash.values(templatesById).map(getVersions));
}
if (minDash.isObject(id)) {
const element = id;
return minDash.filter(this._getTemplateVerions(null, options), function(template) {
return ModelUtil.isAny(element, template.appliesTo);
}) || [];
}
if (minDash.isString(id)) {
return templatesById[ id ] && getVersions(templatesById[ id ]);
}
throw new Error('argument must be of type {string|djs.model.Base|undefined}');
}
_getTemplateId(element) {
return getTemplateId(element);
}
_getTemplateVersion(element) {
return getTemplateVersion(element);
}
/**
* Apply element template to a given element.
*
* @param {djs.model.Base} element
* @param {ElementTemplate} newTemplate
*
* @return {djs.model.Base} the updated element
*/
applyTemplate(element, newTemplate) {
let action = 'apply';
let payload = { element, newTemplate };
const oldTemplate = this.get(element);
if (oldTemplate && !newTemplate) {
action = 'unlink';
payload = { element };
}
if (newTemplate && oldTemplate && (newTemplate.id === oldTemplate.id)) {
action = 'update';
}
const context = {
element,
newTemplate,
oldTemplate
};
this._commandStack.execute('propertiesPanel.camunda.changeTemplate', context);
this._fire(action, payload);
return context.element;
}
_fire(action, payload) {
return this._eventBus.fire(`elementTemplates.${action}`, payload);
}
/**
* Remove template from a given element.
*
* @param {djs.model.Base} element
*
* @return {djs.model.Base} the updated element
*/
removeTemplate(element) {
this._fire('remove', { element });
const context = {
element
};
this._commandStack.execute('propertiesPanel.removeTemplate', context);
return context.newElement;
}
/**
* Unlink template from a given element.
*
* @param {djs.model.Base} element
*
* @return {djs.model.Base} the updated element
*/
unlinkTemplate(element) {
return this.applyTemplate(element, null);
}
};
ElementTemplates$1.$inject = [
'commandStack',
'eventBus',
'modeling',
'injector',
'config.elementTemplates'
];
/**
* Registry for element templates.
*/
class ElementTemplates extends ElementTemplates$1 {
constructor(templateElementFactory, commandStack, eventBus, modeling, injector, config) {
super(commandStack, eventBus, modeling, injector, config);
this._templateElementFactory = templateElementFactory;
}
_getTemplateId(element) {
return getTemplateId$1(element);
}
_getTemplateVersion(element) {
return getTemplateVersion$1(element);
}
/**
* Create an element based on an element template. This is, for example,
* called from the create-append anything menu.
*
* @param {ElementTemplate} template
* @returns {djs.model.Base}
*/
createElement(template) {
if (!template) {
throw new Error('template is missing');
}
const element = this._templateElementFactory.create(template);
return element;
}
/**
* Apply element template to a given element.
*
* @param {djs.model.Base} element
* @param {ElementTemplate} newTemplate
*
* @return {djs.model.Base} the updated element
*/
applyTemplate(element, newTemplate) {
let action = 'apply';
let payload = { element, newTemplate };
const oldTemplate = this.get(element);
if (oldTemplate && !newTemplate) {
action = 'unlink';
payload = { element };
}
if (newTemplate && oldTemplate && (newTemplate.id === oldTemplate.id)) {
action = 'update';
}
const context = {
element,
newTemplate,
oldTemplate
};
this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', context);
this._eventBus.fire(`elementTemplates.${action}`, payload);
return context.element;
}
}
ElementTemplates.$inject = [
'templateElementFactory',
'commandStack',
'eventBus',
'modeling',
'injector',
'config.elementTemplates',
];
const SUPPORTED_SCHEMA_VERSION$1 = elementTemplatesValidator.getSchemaVersion();
const MORPHABLE_TYPES = [ 'bpmn:Activity', 'bpmn:Event', 'bpmn:Gateway' ];
/**
* A element template validator.
*/
let Validator$1 = class Validator {
constructor(moddle) {
this._templatesById = {};
this._validTemplates = [];
this._errors = [];
this._moddle = moddle;
}
/**
* Adds the templates.
*
* @param {Array<TemplateDescriptor>} templates
*
* @return {Validator}
*/
addAll(templates) {
if (!minDash.isArray(templates)) {
this._logError('templates must be []');
} else {
templates.forEach(this.add, this);
}
return this;
}
/**
* Add the given element template, if it is valid.
*
* @param {TemplateDescriptor} template
*
* @return {Validator}
*/
add(template) {
const err = this._validateTemplate(template);
let id, version;
if (!err) {
id = template.id;
version = template.version || '_';
if (!this._templatesById[ id ]) {
this._templatesById[ id ] = {};
}
this._templatesById[ id ][ version ] = template;
this._validTemplates.push(template);
}
return this;
}
/**
* Validate given template and return error (if any).
*
* @param {TemplateDescriptor} template
*
* @return {Error} validation error, if any
*/
_validateTemplate(template) {
const id = template.id,
version = template.version || '_',
schemaVersion = template.$schema && getSchemaVersion(template.$schema);
// (1) compatibility
if (schemaVersion && (semverCompare(SUPPORTED_SCHEMA_VERSION$1, schemaVersion) < 0)) {
return this._logError(
`unsupported element template schema version <${ schemaVersion }>. Your installation only supports up to version <${ SUPPORTED_SCHEMA_VERSION$1 }>. Please update your installation`,
template
);
}
// (2) versioning
if (this._templatesById[ id ] && this._templatesById[ id ][ version ]) {
if (version === '_') {
return this._logError(`template id <${ id }> already used`, template);
} else {
return this._logError(`template id <${ id }> and version <${ version }> already used`, template);
}
}
// (3) elementType validation
const elementTypeError = this._validateElementType(template);
if (elementTypeError) {
return elementTypeError;
}
// (4) JSON schema compliance
const schemaValidationResult = elementTemplatesValidator.validate(template);
const {
errors: schemaErrors,
valid
} = schemaValidationResult;
if (!valid) {
filteredSchemaErrors(schemaErrors).forEach((error) => {
this._logError(error.message, template);
});
return new Error('invalid template');
}
// (5) engines validation
const enginesError = this._validateEngines(template);
if (enginesError) {
return enginesError;
}
return null;
}
_validateEngines(template) {
let err;
minDash.forEach(template.engines, (rangeStr, engine) => {
if (!semver.validRange(rangeStr)) {
err = this._logError(new Error(
`Engine <${engine}> specifies invalid semver range <${rangeStr}>`
), template);
}
});
return err;
}
/**
* Validate elementType for given template and return error (if any).
*
* @param {TemplateDescriptor} template
*
* @return {Error} validation error, if any
*/
_validateElementType(template) {
if (template.elementType && template.appliesTo) {
const elementType = template.elementType.value,
appliesTo = template.appliesTo;
// (3.1) template can be applied to elementType
// prevents cases where the elementType is not part of appliesTo
if (!appliesTo.find(type => this._isType(elementType, type))) {
return this._logError(`template does not apply to requested element type <${ elementType }>`, template);
}
// (3.2) template only applies to same type of element
// prevent elementTemplates to morph into incompatible types, e.g. Task -> SequenceFlow
for (const sourceType of appliesTo) {
if (!this._canMorph(sourceType, elementType)) {
return this._logError(`can not morph <${sourceType}> into <${elementType}>`, template);
}
}
}
}
/**
* Check if given type is a subtype of given base type.
*
* @param {String} type
* @param {String} baseType
* @returns {Boolean}
*/
_isType(type, baseType) {
const moddleType = this._moddle.getType(type);
return moddleType && (baseType in this._moddle.getElementDescriptor(moddleType).allTypesByName);
}
/**
* Checks if a given type can be morphed into another type.
*
* @param {String} sourceType
* @param {String} targetType
* @returns {Boolean}
*/
_canMorph(sourceType, targetType) {
if (sourceType === targetType) {
return true;
}
const baseType = MORPHABLE_TYPES.find(type => this._isType(sourceType, type));
if (!baseType) {
return false;
}
return this._isType(targetType, baseType);
}
/**
* Log an error for the given template
*
* @param {(String|Error)} err
* @param {TemplateDescriptor} template
*
* @return {Error} logged validation errors
*/
_logError(err, template) {
if (minDash.isString(err)) {
if (template) {
const {
id,
name
} = template;
err = `template(id: <${ id }>, name: <${ name }>): ${ err }`;
}
err = new Error(err);
}
this._errors.push(err);
return err;
}
getErrors() {
return this._errors;
}
getValidTemplates() {
return this._validTemplates;
}
};
// helpers //////////
/**
* Extract schema version from schema URI
*
* @param {String} schemaUri - for example https://unpkg.com/@camunda/element-templates-json-schema@99.99.99/resources/schema.json
*
* @return {String} for example '99.99.99'
*/
function getSchemaVersion(schemaUri) {
const re = /\d+\.\d+\.\d+/g;
const match = schemaUri.match(re);
return match === null ? undefined : match[ 0 ];
}
/**
* Extract only relevant errors of the validation result.
*
* The JSON Schema we use under the hood produces more errors than we need for a
* detected schema violation (for example, unmatched sub-schemas, if-then-rules,
* `oneOf`-definitions ...).
*
* We call these errors "relevant" that have a custom error message defined by us OR
* are basic data type errors.
*
* @param {Array} schemaErrors
*
* @return {Array}
*/
function filteredSchemaErrors(schemaErrors) {
return minDash.filter(schemaErrors, (err) => {
const {
instancePath,
keyword
} = err;
// (1) regular errors are customized from the schema
if (keyword === 'errorMessage') {
return true;
}
// (2) data type errors
// ignore type errors nested in scopes
if (keyword === 'type' && instancePath && !instancePath.startsWith('/scopes/')) {
return true;
}
return false;
});
}
const SUPPORTED_SCHEMA_VERSION = elementTemplatesValidator.getZeebeSchemaVersion();
const SUPPORTED_SCHEMA_PACKAGE = elementTemplatesValidator.getZeebeSchemaPackage();
/**
* A Camunda Cloud element template validator.
*/
class Validator extends Validator$1 {
constructor(moddle) {
super(moddle);
}
/**
* Validate given template and return error (if any).
*
* @param {TemplateDescriptor} template
*
* @return {Error} validation error, if any
*/
_validateTemplate(template) {
const id = template.id,
version = template.version || '_',
schema = template.$schema,
schemaVersion = schema && getSchemaVersion(schema);
// (1) $schema attribute defined
if (!schema) {
return this._logError(
'missing $schema attribute.',
template
);
}
if (!this.isSchemaValid(schema)) {
return this._logError(
`unsupported $schema attribute <${ schema }>.`,
template
);
}
// (2) compatibility
if (schemaVersion && (semverCompare(SUPPORTED_SCHEMA_VERSION, schemaVersion) < 0)) {
return this._logError(
`unsupported element template schema version <${ schemaVersion }>. Your installation only supports up to version <${ SUPPORTED_SCHEMA_VERSION }>. Please update your installation`,
template
);
}
// (3) versioning
if (this._templatesById[ id ] && this._templatesById[ id ][ version ]) {
if (version === '_') {
return this._logError(`template id <${ id }> already used`, template);
} else {
return this._logError(`template id <${ id }> and version <${ version }> already used`, template);
}
}
// (4) elementType validation
const elementTypeError = this._validateElementType(template);
if (elementTypeError) {
return elementTypeError;
}
// (5) JSON schema compliance
const schemaValidationResult = elementTemplatesValidator.validateZeebe(template);
const {
errors: schemaErrors,
valid
} = schemaValidationResult;
if (!valid) {
filteredSchemaErrors(schemaErrors).forEach((error) => {
this._logError(error.message, template);
});
return new Error('invalid template');
}
// (6) engines validation
const enginesError = this._validateEngines(template);
if (enginesError) {
return enginesError;
}
return null;
}
isSchemaValid(schema) {
return schema && schema.includes(SUPPORTED_SCHEMA_PACKAGE);
}
_validateEngines(template) {
let err;
minDash.forEach(template.engines, (rangeStr, engine) => {
if (!semver.validRange(rangeStr)) {
err = this._logError(new Error(
`Engine <${engine}> specifies invalid semver range <${rangeStr}>`
), template);
}
});
return err;
}
}
/**
* The guy responsible for template loading.
*
* Provide the actual templates via the `config.elementTemplates`.
*
* That configuration can either be an array of template
* descriptors or a node style callback to retrieve
* the templates asynchronously.
*
* @param {Array<TemplateDescriptor>|Function} config
* @param {EventBus} eventBus
* @param {ElementTemplates} elementTemplates
* @param {Moddle} moddle
*/
let ElementTemplatesLoader$1 = class ElementTemplatesLoader {
constructor(config, eventBus, elementTemplates, moddle) {
this._loadTemplates;
this._eventBus = eventBus;
this._elementTemplates = elementTemplates;
this._moddle = moddle;
if (minDash.isArray(config) || minDash.isFunction(config)) {
this._loadTemplates = config;
}
if (config && config.loadTemplates) {
this._loadTemplates = config.loadTemplates;
}
eventBus.on('diagram.init', () => {
this.reload();
});
}
reload() {
const loadTemplates = this._loadTemplates;
// no templates specified
if (minDash.isUndefined(loadTemplates)) {
return;
}
// template loader function specified
if (minDash.isFunction(loadTemplates)) {
return loadTemplates((err, templates) => {
if (err) {
return this._templateErrors([ err ]);
}
this.setTemplates(templates);
});
}
// templates array specified
if (loadTemplates.length) {
return this.setTemplates(loadTemplates);
}
}
setTemplates(templates) {
const elementTemplates = this._elementTemplates,
moddle = this._moddle;
const validator = new Validator$1(moddle).addAll(templates);
const errors = validator.getErrors(),
validTemplates = validator.getValidTemplates();
elementTemplates.set(validTemplates);
if (errors.length) {
this._templateErrors(errors);
}
}
_templateErrors(errors) {
this._elementTemplates._fire('errors', {
errors: errors
});
}
};
ElementTemplatesLoader$1.$inject = [
'config.elementTemplates',
'eventBus',
'elementTemplates',
'moddle'
];
/**
* @param {Object|Array<TemplateDescriptor>|Function} config
* @param {EventBus} eventBus
* @param {ElementTemplates} elementTemplates
* @param {Moddle} moddle
*/
class ElementTemplatesLoader extends ElementTemplatesLoader$1 {
constructor(config, eventBus, elementTemplates, moddle) {
super(config, eventBus, elementTemplates, moddle);
this._elementTemplates = elementTemplates;
}
setTemplates(templates) {
const elementTemplates = this._elementTemplates,
moddle = this._moddle;
const validator = new Validator(moddle).addAll(templates);
const errors = validator.getErrors(),
validTemplates = validator.getValidTemplates();
elementTemplates.set(validTemplates);
if (errors.length) {
this._templateErrors(errors);
}
}
}
ElementTemplatesLoader.$inject = [
'config.elementTemplates',
'eventBus',
'elementTemplates',
'moddle'
];
/**
* Create a new element and set its parent.
*
* @param {String} elementType of the new element
* @param {Object} properties of the new element in key-value pairs
* @param {moddle.object} parent of the new element
* @param {BpmnFactory} factory which creates the new element
*
* @returns {djs.model.Base} element which is created
*/
function createElement(elementType, properties, parent, factory) {
const element = factory.create(elementType, properties);
if (parent) {
element.$parent = parent;
}
return element;
}
/**
* generate a semantic id with given prefix
*/
function nextId(prefix) {
const ids = new Ids([ 32,32,1 ]);
return ids.nextPrefixed(prefix);
}
function getRoot(businessObject) {
let parent = businessObject;
while (parent.$parent) {
parent = parent.$parent;
}
return parent;
}
/**
* Create an input parameter representing the given
* binding and value.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createInputParameter$1(binding, value, bpmnFactory) {
const {
name
} = binding;
return bpmnFactory.create('zeebe:Input', {
source: value,
target: name
});
}
/**
* Create an output parameter representing the given
* binding and value.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createOutputParameter$1(binding, value, bpmnFactory) {
const {
source
} = binding;
return bpmnFactory.create('zeebe:Output', {
source,
target: value
});
}
/**
* Create a task header representing the given
* binding and value.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createTaskHeader(binding, value, bpmnFactory) {
const {
key
} = binding;
return bpmnFactory.create('zeebe:Header', {
key,
value
});
}
/**
* Create a task definition representing the given value.
*
* @param {object} attrs
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createTaskDefinition(attrs = {}, bpmnFactory) {
return bpmnFactory.create('zeebe:TaskDefinition', attrs);
}
/**
* Create zeebe:Property from the given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createZeebeProperty(binding, value = '', bpmnFactory) {
const { name } = binding;
return bpmnFactory.create('zeebe:Property', {
name,
value
});
}
/**
* Retrieves whether an element should be updated for a given property.
*
* That matches once
* a) the property value is not empty, or
* b) the property is not optional
*
* @param {String} value
* @param {Object} property
* @returns {Boolean}
*/
function shouldUpdate(value, property) {
const { optional } = property;
return value || !optional;
}
/**
* Gets or, in case not existent, creates extension element for given element.
*
* @param {djs.model.Base} element
* @param {String} type
* @param {BpmnFactory} bpmnFactory
* @returns {ModdleElement}
*/
function ensureExtension(element, type, bpmnFactory) {
const businessObject = ModelUtil.getBusinessObject(element);
let extensionElements = businessObject.get('extensionElements');
if (!extensionElements) {
extensionElements = createElement('bpmn:ExtensionElements', {}, businessObject, bpmnFactory);
businessObject.set('extensionElements', extensionElements);
}
let extension = findExtension$1(extensionElements, type);
if (!extension) {
extension = bpmnFactory.create(type);
extension.$parent = extensionElements;
extensionElements.get('values').push(extension);
}
return extension;
}
const PROPERTY_TYPE = 'property';
const ZEBBE_PROPERTY_TYPE = 'zeebe:property';
const ZEBBE_INPUT_TYPE = 'zeebe:input';
const ZEEBE_OUTPUT_TYPE = 'zeebe:output';
const ZEEBE_PROPERTY_TYPE = 'zeebe:property';
const ZEEBE_TASK_DEFINITION_TYPE_TYPE = 'zeebe:taskDefinition:type';
const ZEEBE_TASK_DEFINITION = 'zeebe:taskDefinition';
const ZEEBE_TASK_HEADER_TYPE = 'zeebe:taskHeader';
const MESSAGE_PROPERTY_TYPE = 'bpmn:Message#property';
const MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE = 'bpmn:Message#zeebe:subscription#property';
const ZEEBE_CALLED_ELEMENT = 'zeebe:calledElement';
const ZEEBE_LINKED_RESOURCE_PROPERTY = 'zeebe:linkedResource';
const ZEEBE_USER_TASK = 'zeebe:userTask';
const ZEEBE_CALLED_DECISION = 'zeebe:calledDecision';
const ZEEBE_FORM_DEFINITION = 'zeebe:formDefinition';
const ZEEBE_SCRIPT_TASK = 'zeebe:script';
const ZEEBE_ASSIGNMENT_DEFINITION = 'zeebe:assignmentDefinition';
const EXTENSION_BINDING_TYPES = [
MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE,
ZEBBE_INPUT_TYPE,
ZEEBE_OUTPUT_TYPE,
ZEEBE_PROPERTY_TYPE,
ZEEBE_TASK_DEFINITION_TYPE_TYPE,
ZEEBE_TASK_DEFINITION,
ZEEBE_TASK_HEADER_TYPE,
ZEEBE_CALLED_ELEMENT,
ZEEBE_LINKED_RESOURCE_PROPERTY,
ZEEBE_CALLED_DECISION,
ZEEBE_FORM_DEFINITION,
ZEEBE_SCRIPT_TASK,
ZEEBE_ASSIGNMENT_DEFINITION
];
const TASK_DEFINITION_TYPES = [
ZEEBE_TASK_DEFINITION_TYPE_TYPE,
ZEEBE_TASK_DEFINITION
];
const IO_BINDING_TYPES = [
ZEBBE_INPUT_TYPE,
ZEEBE_OUTPUT_TYPE
];
const MESSAGE_BINDING_TYPES = [
MESSAGE_PROPERTY_TYPE,
MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE
];
const PROPERTY_BINDING_TYPES = [
PROPERTY_TYPE,
MESSAGE_PROPERTY_TYPE
];
function getTaskDefinitionPropertyName(binding) {
return binding.type === ZEEBE_TASK_DEFINITION_TYPE_TYPE ? 'type' : binding.property;
}
function removeRootElement(rootElement, injector) {
const modeling = injector.get('modeling'),
canvas = injector.get('canvas'),
bpmnjs = injector.get('bpmnjs');
const element = canvas.getRootElement(),
definitions = bpmnjs.getDefinitions(),
rootElements = definitions.get('rootElements');
const newRootElements = rootElements.filter(e => e !== rootElement);
// short-circuit to prevent unnecessary updates
if (newRootElements.length === rootElements.length) {
return;
}
modeling.updateModdleProperties(element, definitions, {
rootElements: newRootElements
});
}
/**
* Remove message from element and the diagram.
*
* @param {import('bpmn-js/lib/model/Types').Element} element
* @param {import('didi').Injector} injector
*/
function removeMessage(element, injector) {
const modeling = injector.get('modeling');
const bo = getReferringElement(element);
// Event does not have an event definition
if (!bo) {
return;
}
const message = findMessage(bo);
if (!message) {
return;
}
modeling.updateModdleProperties(element, bo, {
messageRef: undefined
});
removeRootElement(message, injector);
}
function getReferringElement(element) {
const bo = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(bo, 'bpmn:Event')) {
return bo.get('eventDefinitions')[0];
}
return bo;
}
const EXPRESSION_TYPES$1 = [
'bpmn:Expression',
'bpmn:FormalExpression'
];
function isExpression(element, propertyName) {
const bo = ModelUtil.getBusinessObject(element);
const descriptor = bo.$descriptor.propertiesByName[propertyName];
return descriptor && EXPRESSION_TYPES$1.includes(descriptor.type);
}
function createExpression(value, parent, bpmnFactory) {
const expression = createElement('bpmn:FormalExpression', { body: value }, parent, bpmnFactory);
return expression;
}
function getExpressionValue(element, propertyName) {
const bo = ModelUtil.getBusinessObject(element);
const expression = bo.get(propertyName);
return expression?.get('body') || undefined;
}
/**
* Applies an element template to an element. Sets `zeebe:modelerTemplate` and
* `zeebe:modelerTemplateVersion`.
*/
let ChangeElementTemplateHandler$1 = class ChangeElementTemplateHandler {
constructor(bpmnFactory, bpmnReplace, commandStack, modeling, moddleCopy, injector) {
this._bpmnFactory = bpmnFactory;
this._bpmnReplace = bpmnReplace;
this._modeling = modeling;
this._moddleCopy = moddleCopy;
this._commandStack = commandStack;
this._injector = injector;
}
/**
* Change an element's template and update its properties as specified in `newTemplate`. Specify
* `oldTemplate` to update from one template to another. If `newTemplate` isn't specified the
* `zeebe:modelerTemplate` and `zeebe:modelerTemplateVersion` properties will be removed from
* the element.
*
* @param {Object} context
* @param {Object} context.element
* @param {Object} [context.oldTemplate]
* @param {Object} [context.newTemplate]
*/
preExecute(context) {
let newTemplate = context.newTemplate,
oldTemplate = context.oldTemplate;
let element = context.element;
// update zeebe:modelerTemplate attribute
this._updateZeebeModelerTemplate(element, newTemplate);
// update zeebe:modelerTemplateIcon
this._updateZeebeModelerTemplateIcon(element, newTemplate);
if (newTemplate) {
// update element type
element = context.element = this._updateElementType(element, oldTemplate, newTemplate);
// update properties
this._updateProperties(element, oldTemplate, newTemplate);
// update zeebe:TaskDefinition
this._updateZeebeTaskDefinition(element, oldTemplate, newTemplate);
// update zeebe:Input and zeebe:Output properties
this._updateZeebeInputOutputParameterProperties(element, oldTemplate, newTemplate);
// update zeebe:Header properties
this._updateZeebeTaskHeaderProperties(element, oldTemplate, newTemplate);
// update zeebe:Property properties
this._updateZeebePropertyProperties(element, oldTemplate, newTemplate);
this._updateMessage(element, oldTemplate, newTemplate);
this._updateCalledElement(element, oldTemplate, newTemplate);
this._updateLinkedResources(element, oldTemplate, newTemplate);
this._updateZeebeUserTask(element, newTemplate);
this._updateCalledDecision(element, oldTemplate, newTemplate);
this._updateZeebeFormDefinition(element, oldTemplate, newTemplate);
this._updateScriptTask(element, oldTemplate, newTemplate);
this._updateZeebeAssignmentDefinition(element, oldTemplate, newTemplate);
}
}
_getOrCreateExtensionElements(element, businessObject = ModelUtil.getBusinessObject(element)) {
const bpmnFactory = this._bpmnFactory,
modeling = this._modeling;
let extensionElements = businessObject.get('extensionElements');
if (!extensionElements) {
extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
values: []
});
extensionElements.$parent = businessObject;
modeling.updateModdleProperties(element, businessObject, {
extensionElements: extensionElements
});
}
return extensionElements;
}
_updateZeebeModelerTemplate(element, newTemplate) {
const modeling = this._modeling;
const newId = newTemplate && newTemplate.id;
const newVersion = newTemplate && newTemplate.version;
if (getTemplateId$1(element) !== newId || getTemplateVersion$1(element) !== newVersion) {
modeling.updateProperties(element, {
'zeebe:modelerTemplate': newId,
'zeebe:modelerTemplateVersion': newVersion
});
}
}
_updateZeebeModelerTemplateIcon(element, newTemplate) {
const modeling = this._modeling;
const newIcon = newTemplate && newTemplate.icon;
const newIconContents = newIcon && newIcon.contents;
if (getTemplateIcon(element) !== newIconContents) {
modeling.updateProperties(element, {
'zeebe:modelerTemplateIcon': newIconContents
});
}
}
_updateProperties(element, oldTemplate, newTemplate) {
const commandStack = this._commandStack;
const businessObject = ModelUtil.getBusinessObject(element);
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'property';
});
// Remove old Properties if no new Properties specified
const propertiesToRemove = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === 'property' && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name);
}) || [];
if (propertiesToRemove.length) {
const payload = propertiesToRemove.reduce((properties, property) => {
properties[property.binding.name] = undefined;
return properties;
}, {});
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: payload
});
}
if (!newProperties.length) {
return;
}
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newPropertyValue = getDefaultValue(newProperty),
changedElement = businessObject;
let properties = {};
if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
return;
}
let assignedValue = newPropertyValue;
if (isExpression(businessObject, newBindingName)) {
assignedValue = createExpression(newPropertyValue, businessObject, this._bpmnFactory);
}
properties[ newBindingName ] = assignedValue;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties
});
});
}
/**
* Update `zeebe:TaskDefinition` properties of specified business object. This
* can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateZeebeTaskDefinition(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: TASK_DEFINITION_TYPES,
extensionType: 'zeebe:TaskDefinition',
getPropertyName: getTaskDefinitionPropertyName
}
);
}
/**
* Update `zeebe:Input` and `zeebe:Output` properties of specified business
* object. Both can only exist in `zeebe:ioMapping` which can exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateZeebeInputOutputParameterProperties(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'zeebe:input' || newBindingType === 'zeebe:output';
});
const businessObject = this._getOrCreateExtensionElements(element);
let ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');
// (1) remove old mappings if no new specified
if (!newProperties.length) {
if (!ioMapping) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: minDash.without(businessObject.get('values'), ioMapping)
}
});
}
if (!ioMapping) {
ioMapping = bpmnFactory.create('zeebe:IoMapping');
ioMapping.$parent = businessObject;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), ioMapping ]
}
});
}
const oldInputs = ioMapping.get('zeebe:inputParameters')
? ioMapping.get('zeebe:inputParameters').slice()
: [];
const oldOutputs = ioMapping.get('zeebe:outputParameters')
? ioMapping.get('zeebe:outputParameters').slice()
: [];
let propertyName;
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
inputOrOutput = findBusinessObject(businessObject, newProperty),
newPropertyValue = getDefaultValue(newProperty),
newBinding = newProperty.binding,
newBindingType = newBinding.type;
let newInputOrOutput,
properties;
// (2) update old inputs and outputs
if (inputOrOutput) {
// (2a) exclude old inputs and outputs from cleanup, unless
// a) optional and has empty value, and
// b) not changed
if (
shouldUpdate(newPropertyValue, newProperty) ||
shouldKeepValue(inputOrOutput, oldProperty, newProperty)
) {
if (ModelUtil.is(inputOrOutput, 'zeebe:Input')) {
remove$1(oldInputs, inputOrOutput);
} else {
remove$1(oldOutputs, inputOrOutput);
}
}
// (2a) do updates (unless changed)
if (!shouldKeepValue(inputOrOutput, oldProperty, newProperty)) {
if (ModelUtil.is(inputOrOutput, 'zeebe:Input')) {
properties = {
source: newPropertyValue
};
} else {
properties = {
target: newPropertyValue
};
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: inputOrOutput,
properties
});
}
}
// (3) add new inputs and outputs (unless optional)
else if (shouldUpdate(newPropertyValue, newProperty)) {
if (newBindingType === 'zeebe:input') {
propertyName = 'inputParameters';
newInputOrOutput = createInputParameter$1(newBinding, newPropertyValue, bpmnFactory);
} else {
propertyName = 'outputParameters';
newInputOrOutput = createOutputParameter$1(newBinding, newPropertyValue, bpmnFactory);
}
newInputOrOutput.$parent = ioMapping;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: ioMapping,
properties: {
[ propertyName ]: [ ...ioMapping.get(propertyName), newInputOrOutput ]
}
});
}
});
// (4) remove old inputs and outputs
if (oldInputs.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: ioMapping,
properties: {
inputParameters: minDash.without(ioMapping.get('inputParameters'), inputParameter => oldInputs.includes(inputParameter))
}
});
}
if (oldOutputs.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: ioMapping,
properties: {
outputParameters: minDash.without(ioMapping.get('outputParameters'), outputParameter => oldOutputs.includes(outputParameter))
}
});
}
}
/**
* Update `zeebe:Header` properties of specified business
* object. Those can only exist in `zeebe:taskHeaders` which can exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateZeebeTaskHeaderProperties(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'zeebe:taskHeader';
});
const businessObject = this._getOrCreateExtensionElements(element);
let taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');
// (1) remove old headers if no new specified
if (!newProperties.length) {
if (!taskHeaders) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: minDash.without(businessObject.get('values'), taskHeaders)
}
});
}
if (!taskHeaders) {
taskHeaders = bpmnFactory.create('zeebe:TaskHeaders');
taskHeaders.$parent = businessObject;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), taskHeaders ]
}
});
}
const oldHeaders = taskHeaders.get('zeebe:values')
? taskHeaders.get('zeebe:values').slice()
: [];
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
oldHeader = findBusinessObject(businessObject, newProperty),
newPropertyValue = getDefaultValue(newProperty),
newBinding = newProperty.binding;
// (2) update old headers
if (oldHeader) {
if (!shouldKeepValue(oldHeader, oldProperty, newProperty)) {
const properties = {
value: newPropertyValue
};
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldHeader,
properties
});
}
remove$1(oldHeaders, oldHeader);
}
// (3) add new (non-empty) headers
else if (newPropertyValue) {
const newHeader = createTaskHeader(newBinding, newPropertyValue, bpmnFactory);
newHeader.$parent = taskHeaders;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: taskHeaders,
properties: {
values: [ ...taskHeaders.get('values'), newHeader ]
}
});
}
});
// (4) remove old headers
if (oldHeaders.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: taskHeaders,
properties: {
values: minDash.without(taskHeaders.get('values'), header => oldHeaders.includes(header))
}
});
}
}
/**
* Update zeebe:Property properties of zeebe:Properties extension element.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateZeebePropertyProperties(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'zeebe:property';
});
const businessObject = this._getOrCreateExtensionElements(element);
let zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
// (1) remove old zeebe:Properties if no new zeebe:Property properties
if (!newProperties.length) {
if (!zeebeProperties) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: minDash.without(businessObject.get('values'), zeebeProperties)
}
});
}
if (!zeebeProperties) {
zeebeProperties = bpmnFactory.create('zeebe:Properties');
zeebeProperties.$parent = businessObject;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), zeebeProperties ]
}
});
}
const oldZeebeProperties = zeebeProperties.get('properties')
? zeebeProperties.get('properties').slice()
: [];
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
oldZeebeProperty = findBusinessObject(businessObject, newProperty),
newPropertyValue = getDefaultValue(newProperty),
newBinding = newProperty.binding;
// (2) update old zeebe:Property
if (oldZeebeProperty) {
if (shouldUpdate(newPropertyValue, newProperty)
|| shouldKeepValue(oldZeebeProperty, oldProperty, newProperty)) {
remove$1(oldZeebeProperties, oldZeebeProperty);
}
if (!shouldKeepValue(oldZeebeProperty, oldProperty, newProperty)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldZeebeProperty,
properties: {
value: newPropertyValue
}
});
}
}
// (3) add new zeebe:Property
else if (shouldUpdate(newPropertyValue, newProperty)) {
const newProperty = createZeebeProperty(newBinding, newPropertyValue, bpmnFactory);
newProperty.$parent = zeebeProperties;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: zeebeProperties,
properties: {
properties: [ ...zeebeProperties.get('properties'), newProperty ]
}
});
}
});
// (4) remove old zeebe:Property
if (oldZeebeProperties.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: zeebeProperties,
properties: {
properties: minDash.without(zeebeProperties.get('properties'), zeebeProperty => oldZeebeProperties.includes(zeebeProperty))
}
});
}
}
_updateMessage(element, oldTemplate, newTemplate) {
// update bpmn:Message properties
this._updateMessageProperties(element, oldTemplate, newTemplate);
// update bpmn:Message zeebe:subscription properties
this._updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate);
this._updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate);
if (!hasMessageProperties(newTemplate)) {
removeMessage(element, this._injector);
}
}
/**
* Update bpmn:Message properties.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateMessageProperties(element, oldTemplate, newTemplate) {
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === MESSAGE_PROPERTY_TYPE;
});
const removedProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === MESSAGE_PROPERTY_TYPE && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name);
}) || [];
let message = this._getMessage(element);
message && removedProperties.forEach((removedProperty) => {
this._modeling.updateModdleProperties(element, message, {
[removedProperty.binding.name]: undefined
});
});
if (!newProperties.length) {
return;
}
message = this._ensureMessage(element, newTemplate);
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newPropertyValue = getDefaultValue(newProperty),
changedElement = message;
let properties = {};
if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
return;
}
properties[ newBindingName ] = newPropertyValue;
this._modeling.updateModdleProperties(element, changedElement, properties);
});
}
/**
* Update bpmn:Message#zeebe:subscription properties.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateMessageZeebeSubscriptionProperties(element, oldTemplate, newTemplate) {
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE;
});
const removedProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE && !newProperties.find((newProperty) => newProperty.binding.name === oldProperty.binding.name);
}) || [];
if (!newProperties.length && !removedProperties.length) {
return;
}
let message = this._getMessage(element);
// no new properties and no message -> nothing to do
if (!newProperties.length && !message) {
return;
}
message = this._ensureMessage(element, newTemplate);
const messageExtensionElements = this._getOrCreateExtensionElements(element, message);
const zeebeSubscription = this._getSubscription(element, message);
const propertiesToSet = newProperties.reduce((properties, newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newPropertyValue = getDefaultValue(newProperty),
changedElement = zeebeSubscription;
if (shouldKeepValue(changedElement, oldProperty, newProperty)) {
return properties;
}
properties[ newBindingName ] = newPropertyValue;
return properties;
}, {});
// Update zeebe Subscription
if (zeebeSubscription) {
this._modeling.updateModdleProperties(element, zeebeSubscription,
propertiesToSet
);
} else {
// create new Subscription
const newSubscription = createElement('zeebe:Subscription', propertiesToSet, message, this._bpmnFactory);
this._modeling.updateModdleProperties(element, messageExtensionElements, {
values: [ ...messageExtensionElements.get('values'), newSubscription ]
});
}
// Remove old properties
if (!oldTemplate || !zeebeSubscription) {
return;
}
const propertiesToRemove = removedProperties.reduce((properties, removedProperty) => {
properties[ removedProperty.binding.name ] = undefined;
return properties;
}, {});
this._modeling.updateModdleProperties(element, zeebeSubscription,
propertiesToRemove
);
}
_updateZeebeModelerTemplateOnReferencedElement(element, oldTemplate, newTemplate) {
const businessObject = ModelUtil.getBusinessObject(element);
const message = findMessage(businessObject);
if (!message) {
return;
}
if (getTemplateId$1(message) === newTemplate.id) {
return;
}
this._modeling.updateModdleProperties(element, message, {
'zeebe:modelerTemplate': newTemplate.id
});
}
_getSubscription(element, bo) {
const extensionElements = this._getOrCreateExtensionElements(element, bo);
const extension = findExtension$1(extensionElements, 'zeebe:Subscription');
if (extension) {
return extension;
}
}
_createMessage(element, template) {
let bo = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(bo, 'bpmn:Event')) {
bo = bo.get('eventDefinitions')[0];
}
const message = this._bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': template.id });
message.$parent = getRoot(bo);
this._modeling.updateModdleProperties(element, bo, { messageRef: message });
return message;
}
_getMessage(element) {
let bo = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(bo, 'bpmn:Event')) {
bo = bo.get('eventDefinitions')[0];
}
return bo && bo.get('messageRef');
}
_ensureMessage(element, template) {
const message = this._getMessage(element);
// message is already templated, so we use it
if (message && message.get('zeebe:modelerTemplate')) {
return message;
}
const newMessage = this._createMessage(element, template);
// no message is set on the element, so no properties to copy
if (!message) {
return newMessage;
}
return this._moddleCopy.copyElement(message, newMessage, [ 'name', 'extensionElements' ]);
}
/**
* Update `zeebe:CalledElement` properties of specified business object. This
* can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCalledElement(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: [ ZEEBE_CALLED_ELEMENT ],
extensionType: 'zeebe:CalledElement',
getPropertyName: (binding) => binding.property
}
);
}
/**
* Update `zeebe:CalledDecision` properties of specified business object. This
* can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCalledDecision(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: [ ZEEBE_CALLED_DECISION ],
extensionType: 'zeebe:CalledDecision',
getPropertyName: (binding) => binding.property
}
);
}
/**
* Update `zeebe:Script` properties of specified business object. This
* can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateScriptTask(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: [ ZEEBE_SCRIPT_TASK ],
extensionType: 'zeebe:Script',
getPropertyName: (binding) => binding.property
}
);
}
_updateZeebeAssignmentDefinition(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: [ ZEEBE_ASSIGNMENT_DEFINITION ],
extensionType: 'zeebe:AssignmentDefinition',
getPropertyName: (binding) => binding.property
}
);
}
/**
* Replaces the element with the specified elementType.
* Takes into account the eventDefinition for events.
*
* @param {djs.model.Base} element
* @param {Object} newTemplate
*/
_updateElementType(element, oldTemplate, newTemplate) {
// determine new task type
const newType = newTemplate.elementType;
if (!newType) {
return element;
}
// Do not replace if the element type did not change
if (!shouldUpdateElementType(element, oldTemplate, newType)) {
return element;
}
const replacement = { type: newType.value };
if (newType.eventDefinition) {
replacement.eventDefinitionType = newType.eventDefinition;
}
const replacedElement = this._bpmnReplace.replaceElement(element, replacement);
return replacedElement;
}
_updateLinkedResources(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newLinkedResources = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'zeebe:linkedResource';
});
const extensionElements = this._getOrCreateExtensionElements(element);
let linkedResources = findExtension$1(extensionElements, 'zeebe:LinkedResources');
// (1) remove linkedResourcess if no new specified
if (!newLinkedResources.length) {
if (!linkedResources) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: minDash.without(extensionElements.get('values'), linkedResources)
}
});
return;
}
if (!linkedResources) {
linkedResources = bpmnFactory.create('zeebe:LinkedResources');
linkedResources.$parent = extensionElements;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), linkedResources ]
}
});
}
const unusedLinkedResources = linkedResources.get('values').slice();
const unusedResourceProperties = (oldTemplate?.properties.slice() || [])
.filter((property) => property.binding.type === ZEEBE_LINKED_RESOURCE_PROPERTY);
newLinkedResources.forEach((newLinkedResource) => {
const oldProperty = findOldProperty$1(oldTemplate, newLinkedResource),
oldLinkedResource = findBusinessObject(extensionElements, newLinkedResource),
newPropertyValue = getDefaultValue(newLinkedResource),
newBinding = newLinkedResource.binding;
if (oldProperty) {
remove$1(unusedResourceProperties, oldProperty);
}
// (2) update old LinkesResources
if (oldLinkedResource) {
if (
shouldUpdate(newPropertyValue, newLinkedResource)
|| shouldKeepValue(oldLinkedResource, oldProperty, newLinkedResource)
) {
remove$1(unusedLinkedResources, oldLinkedResource);
}
if (!shouldKeepValue(oldLinkedResource, oldProperty, newLinkedResource)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldLinkedResource,
properties: {
[newBinding.property]: newPropertyValue
}
});
}
}
// (3) add new linkedResources
else if (shouldUpdate(newPropertyValue, newLinkedResource)) {
const newProperties = {
linkName: newBinding.linkName,
[newBinding.property]: newPropertyValue
};
const newLinkedResource = createElement('zeebe:LinkedResource', newProperties, extensionElements, bpmnFactory);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: linkedResources,
properties: {
values: [ ...linkedResources.get('values'), newLinkedResource ]
}
});
}
});
// (4) remove unused linkedResources
if (unusedLinkedResources.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: linkedResources,
properties: {
values: minDash.without(linkedResources.get('values'), linkedResource => unusedLinkedResources.includes(linkedResource))
}
});
}
// (5) remove unused resource properties
unusedResourceProperties.forEach((unusedResourceProperty) => {
const oldLinkedResource = findBusinessObject(extensionElements, unusedResourceProperty);
const oldBinding = unusedResourceProperty.binding;
// No property was reused and element was removed in previous step
if (!oldLinkedResource) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldLinkedResource,
properties: {
[oldBinding.property]: undefined
}
});
});
}
_updateZeebeUserTask = function(element, newTemplate) {
const commandStack = this._commandStack;
const bpmnFactory = this._bpmnFactory;
// check if template has zeebe:userTask binding property
const hasBinding = newTemplate.properties.some((property) =>
property.binding.type === ZEEBE_USER_TASK);
// check if element has zeebe:UserTask extension element
const extensionElements = ModelUtil.getBusinessObject(element).get('extensionElements');
const userTaskExtension = findExtension$1(element, 'zeebe:UserTask');
if (newTemplate.elementType?.value !== 'bpmn:UserTask') {
return;
}
// remove zeebe:UserTask if no binding
if (userTaskExtension) {
!hasBinding && commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: minDash.without(extensionElements.get('values'), userTaskExtension)
}
});
return;
}
if (!hasBinding) {
return;
}
// create new zeebe:UserTask extension element
const zeebeUserTask = bpmnFactory.create('zeebe:UserTask');
zeebeUserTask.$parent = extensionElements;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), zeebeUserTask ]
}
});
};
/**
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateZeebeFormDefinition = function(element, oldTemplate, newTemplate) {
this._updateSingleExtensionElement(
element,
oldTemplate,
newTemplate,
{
bindingTypes: [ ZEEBE_FORM_DEFINITION ],
extensionType: 'zeebe:FormDefinition',
getPropertyName: (binding) => binding.property
}
);
};
/**
* Generic handler for updating extension elements that are single instances with properties.
*
* @example
* For this template
* ```json
* {
* properties: [
* { binding: { type: 'zeebe:taskDefinition', property: 'type' }, value: 'myTaskType' },
* {binding: { type: 'zeebe:taskDefinition', property: 'retries' }, value: 3}
* ]
* }
*```
* `getPropertyName` should return `binding.property`.
*
* `bindingTypes` should be an array of binding types to consider, e.g. `['zeebe:taskDefinition']`.
*
* `extensionType` should be the type of the extension element to update, e.g. `'zeebe:TaskDefinition'`.
*
* The resulting XML will look like this:
*
* ```xml
* <bpmn:extensionElements>
* <zeebe:taskDefinition type="myTaskType" retries="3" />
* </bpmn:extensionElements>
*```
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
* @param {Object} opts
* @param {Array<string>} opts.bindingTypes - binding types to consider
* @param {string} opts.extensionType - type of the extension element to update
* @param {(binding:object) => string} opts.getPropertyName - function to get the property name for the binding
*/
_updateSingleExtensionElement(element, oldTemplate, newTemplate, opts) {
const { bindingTypes, extensionType, getPropertyName } = opts;
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return bindingTypes.includes(newBindingType);
});
const businessObject = this._getOrCreateExtensionElements(element);
let extensionElement = findExtension$1(businessObject, extensionType);
// (1) Remove extension if no new properties
if (!newProperties.length) {
if (!extensionElement) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: minDash.without(businessObject.get('values'), extensionElement)
}
});
return;
}
// If there are new properties:
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty$1(oldTemplate, newProperty),
newPropertyValue = getDefaultValue(newProperty),
newBinding = newProperty.binding,
propertyName = getPropertyName(newBinding);
// (2) Update old extension with new property values
if (extensionElement) {
if (!shouldKeepValue(extensionElement, oldProperty, newProperty)) {
const properties = {
[propertyName]: newPropertyValue
};
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElement,
properties
});
}
}
// (3) Add new extension with properties if it does not exist
else {
const properties = {
[propertyName]: newPropertyValue
};
extensionElement = bpmnFactory.create(extensionType, properties);
extensionElement.$parent = businessObject;
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), extensionElement ]
}
});
}
});
// (4) Remove properties no longer templated
const oldProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type,
oldPropertyName = getPropertyName(oldBinding);
return bindingTypes.includes(oldBindingType) &&
!newProperties.find((newProperty) => oldPropertyName === getPropertyName(newProperty.binding));
}) || [];
oldProperties.forEach((oldProperty) => {
const properties = {
[ getPropertyName(oldProperty.binding) ]: undefined
};
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElement,
properties
});
});
}
};
ChangeElementTemplateHandler$1.$inject = [
'bpmnFactory',
'bpmnReplace',
'commandStack',
'modeling',
'moddleCopy',
'injector'
];
// helpers //////////
/**
* Find business object matching specified property.
*
* @param {djs.model.Base|ModdleElement} element
* @param {Object} property
*
* @returns {ModdleElement}
*/
function findBusinessObject(element, property) {
const businessObject = ModelUtil.getBusinessObject(element);
const binding = property.binding,
bindingType = binding.type;
if (TASK_DEFINITION_TYPES.includes(bindingType)) {
return findExtension$1(businessObject, 'zeebe:TaskDefinition');
}
if (bindingType === 'zeebe:input' || bindingType === 'zeebe:output') {
const extensionElements = findExtension$1(businessObject, 'zeebe:IoMapping');
if (!extensionElements) {
return;
}
if (bindingType === 'zeebe:input') {
return minDash.find(extensionElements.get('zeebe:inputParameters'), function(input) {
return input.get('zeebe:target') === binding.name;
});
} else {
return minDash.find(extensionElements.get('zeebe:outputParameters'), function(output) {
return output.get('zeebe:source') === binding.source;
});
}
}
if (bindingType === 'zeebe:taskHeader') {
const extensionElements = findExtension$1(businessObject, 'zeebe:TaskHeaders');
if (!extensionElements) {
return;
}
return minDash.find(extensionElements.get('zeebe:values'), function(value) {
return value.get('zeebe:key') === binding.key;
});
}
if (bindingType === 'zeebe:property') {
const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
if (!zeebeProperties) {
return;
}
return zeebeProperties.get('properties').find((value) => {
return value.get('name') === binding.name;
});
}
if (bindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
const linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
if (!linkedResources) {
return;
}
return linkedResources.get('values').find((value) => {
return value.get('linkName') === binding.linkName;
});
}
}
/**
* Find old property matching specified new property.
*
* @param {Object} oldTemplate
* @param {Object} newProperty
*
* @returns {Object}
*/
function findOldProperty$1(oldTemplate, newProperty) {
if (!oldTemplate) {
return;
}
const oldProperties = oldTemplate.properties,
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newBindingType = newBinding.type;
if (newBindingType === 'property') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
return oldBindingType === 'property' && oldBindingName === newBindingName;
});
}
if (TASK_DEFINITION_TYPES.includes(newBindingType)) {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldPropertyName = getTaskDefinitionPropertyName(oldBinding),
newPropertyName = getTaskDefinitionPropertyName(newBinding);
return oldPropertyName === newPropertyName;
});
}
if (newBindingType === 'zeebe:input') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'zeebe:input') {
return;
}
return oldBindingName === newBindingName;
});
}
if (newBindingType === 'zeebe:output') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'zeebe:output') {
return;
}
return oldBinding.source === newBinding.source;
});
}
if (newBindingType === 'zeebe:taskHeader') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'zeebe:taskHeader') {
return;
}
return oldBinding.key === newBinding.key;
});
}
if (newBindingType === 'zeebe:property') {
return oldProperties.find((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'zeebe:property') {
return;
}
return oldBinding.name === newBinding.name;
});
}
if (newBindingType === MESSAGE_PROPERTY_TYPE) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== MESSAGE_PROPERTY_TYPE) {
return;
}
return oldBinding.name === newBinding.name;
});
}
if (newBindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
return;
}
return oldBinding.name === newBinding.name;
});
}
if (newBindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== ZEEBE_LINKED_RESOURCE_PROPERTY) {
return;
}
return oldBinding.linkName === newBinding.linkName && oldBinding.property === newBinding.property;
});
}
if (newBindingType === ZEEBE_CALLED_DECISION) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== ZEEBE_CALLED_DECISION) {
return;
}
return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
});
}
if (newBindingType === ZEEBE_SCRIPT_TASK) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== ZEEBE_SCRIPT_TASK) {
return;
}
return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
});
}
if (newBindingType === ZEEBE_FORM_DEFINITION) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== ZEEBE_FORM_DEFINITION) {
return;
}
return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
});
}
if (newBindingType === ZEEBE_ASSIGNMENT_DEFINITION) {
return oldProperties.find(oldProperty => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== ZEEBE_ASSIGNMENT_DEFINITION) {
return;
}
return oldBindingType === newBindingType && oldBinding.property === newBinding.property;
});
}
}
/**
* Check whether the existing property should be kept. This is the case if
* - an old template was set and the value differs from the default
* - no template was set but the property was set manually
*
* @param {djs.model.Base|ModdleElement} element
* @param {Object} oldProperty
* @param {Object} newProperty
*
* @returns {boolean}
*/
function shouldKeepValue(element, oldProperty, newProperty) {
// "Hidden" values are treated as a constant
if (newProperty.type === 'Hidden') {
return false;
}
// Dropdowns should keep existing configuration
// cf. https://github.com/bpmn-io/bpmn-js-properties-panel/issues/767
if (newProperty.type === 'Dropdown') {
const currentValue = getPropertyValue$1(element, newProperty);
// only keep value if old value is a valid option
return newProperty.choices && newProperty.choices.some(
(choice) => choice.value === currentValue
);
}
// keep existing old property if
// user changed it from the original
if (oldProperty) {
return propertyChanged$1(element, oldProperty);
}
// keep existing property value
return !!(getPropertyValue$1(element, newProperty));
}
/**
* Check whether property was changed after being set by template.
*
* @param {djs.model.Base|ModdleElement} element
* @param {Object} oldProperty
*
* @returns {boolean}
*/
function propertyChanged$1(element, oldProperty) {
const oldPropertyValue = oldProperty.value;
return getPropertyValue$1(element, oldProperty) !== oldPropertyValue;
}
function getPropertyValue$1(element, property) {
const businessObject = ModelUtil.getBusinessObject(element);
if (!businessObject) {
return;
}
const binding = property.binding,
bindingName = binding.name,
bindingType = binding.type,
bindingProperty = binding.property;
if (bindingType === 'property') {
return businessObject.get(bindingName);
}
if (TASK_DEFINITION_TYPES.includes(bindingType)) {
return businessObject.get(getTaskDefinitionPropertyName(binding));
}
if (bindingType === 'zeebe:input') {
return businessObject.get('zeebe:source');
}
if (bindingType === 'zeebe:output') {
return businessObject.get('zeebe:target');
}
if (bindingType === 'zeebe:taskHeader') {
return businessObject.get('zeebe:value');
}
if (bindingType === 'zeebe:property') {
return businessObject.get('zeebe:value');
}
if (bindingType === MESSAGE_PROPERTY_TYPE) {
return businessObject.get(bindingName);
}
if (bindingType === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
return businessObject.get(bindingName);
}
if (bindingType === ZEEBE_LINKED_RESOURCE_PROPERTY) {
return businessObject.get(bindingProperty);
}
if (bindingType === ZEEBE_CALLED_DECISION) {
return businessObject.get(bindingProperty);
}
if (bindingType === ZEEBE_CALLED_ELEMENT) {
return businessObject.get(bindingProperty);
}
if (bindingType === ZEEBE_FORM_DEFINITION) {
return businessObject.get(bindingProperty);
}
if (bindingType === ZEEBE_SCRIPT_TASK) {
return businessObject.get(bindingProperty);
}
if (bindingType === ZEEBE_ASSIGNMENT_DEFINITION) {
return businessObject.get(bindingProperty);
}
}
function remove$1(array, item) {
const index = array.indexOf(item);
if (index < 0) {
return array;
}
array.splice(index, 1);
return array;
}
function hasMessageProperties(template) {
return template.properties.some(p => MESSAGE_BINDING_TYPES.includes(p.binding.type));
}
function shouldUpdateElementType(element, oldTemplate, newType) {
// Never reuse existing eventDefinition when applying a new template
if (!oldTemplate && newType.eventDefinition) {
return true;
}
const oldType = oldTemplate && oldTemplate.elementType || {
value: element.type,
eventDefinition: getEventDefinitionType(element)
};
// Do not update if the element type did not change
if (oldType && oldType.value === newType.value && oldType.eventDefinition === newType.eventDefinition) {
return false;
}
return true;
}
function getEventDefinitionType(element) {
const businessObject = ModelUtil.getBusinessObject(element);
if (!businessObject.eventDefinitions) {
return;
}
const eventDefinition = businessObject.eventDefinitions[ 0 ];
if (!eventDefinition) {
return;
}
return eventDefinition.$type;
}
let RemoveElementTemplateHandler$1 = class RemoveElementTemplateHandler {
constructor(
modeling,
elementFactory,
elementRegistry,
canvas,
bpmnFactory,
replace,
commandStack,
moddleCopy
) {
this._modeling = modeling;
this._elementFactory = elementFactory;
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._bpmnFactory = bpmnFactory;
this._replace = replace;
this._commandStack = commandStack;
this._moddleCopy = moddleCopy;
}
preExecute(context) {
const {
element
} = context;
if (element.parent) {
context.newElement = this._removeTemplate(element);
} else {
context.newElement = this._removeRootTemplate(element);
}
}
_removeTemplate(element) {
const replace = this._replace;
const businessObject = ModelUtil.getBusinessObject(element);
const type = businessObject.$type,
eventDefinitionType = this._getEventDefinitionType(businessObject);
const newBusinessObject = this._createNewBusinessObject(element);
return replace.replaceElement(element,
{
type: type,
businessObject: newBusinessObject,
eventDefinitionType: eventDefinitionType,
},
{
createElementsBehavior: false
}
);
}
/**
* Remove template from a given element.
*
* @param {djs.model.Base} element
*
* @return {djs.model.Base} the updated element
*/
_removeRootTemplate(element) {
var modeling = this._modeling,
elementFactory = this._elementFactory,
elementRegistry = this._elementRegistry,
canvas = this._canvas;
// We are inside a collapsed subprocess, move up to the parent before replacing the collapsed object
if (DrilldownUtil.isPlane(element)) {
const shapeId = DrilldownUtil.getShapeIdFromPlane(element);
const shape = elementRegistry.get(shapeId);
if (shape && shape !== element) {
canvas.setRootElement(canvas.findRoot(shape));
return this._removeTemplate(shape);
}
}
const businessObject = ModelUtil.getBusinessObject(element);
const type = businessObject.$type;
const newBusinessObject = this._createNewBusinessObject(element);
const newRoot = elementFactory.create('root', {
type: type,
businessObject: newBusinessObject
});
this._commandStack.execute('canvas.updateRoot', {
newRoot: newRoot,
oldRoot: element
});
modeling.moveElements(element.children, { x: 0, y: 0 }, newRoot);
return newRoot;
}
_getEventDefinitionType(businessObject) {
if (!businessObject.eventDefinitions) {
return null;
}
const eventDefinition = businessObject.eventDefinitions[ 0 ];
if (!eventDefinition) {
return null;
}
return eventDefinition.$type;
}
_createNewBusinessObject(element) {
const bpmnFactory = this._bpmnFactory;
const bo = ModelUtil.getBusinessObject(element),
newBo = bpmnFactory.create(bo.$type),
label = LabelUtil.getLabel(element);
// Make we we keep general properties unrelated to the template.
this._copyProperties(bo, newBo, [ 'documentation' ]);
this._copyExtensionElements(bo, newBo, [ 'zeebe:ExecutionListeners' ]);
if (!label) {
return newBo;
}
if (ModelUtil.is(element, 'bpmn:Group')) {
newBo.categoryValueRef = bpmnFactory.create('bpmn:CategoryValue');
}
LabelUtil.setLabel({ businessObject: newBo }, label);
return newBo;
}
/**
* Copy specified properties to the target business object.
*
* @param {ModdleElement} source
* @param {ModdleElement} target
* @param {Array<string>} properties
*/
_copyProperties(source, target, properties) {
const copy = this._moddleCopy;
properties.forEach(propertyName => {
const property = source.get(propertyName);
if (property) {
const propertyCopy = copy.copyProperty(property, target, propertyName);
target.set(propertyName, propertyCopy);
}
});
}
/**
* Copy extension elements of specified types to the target business object.
*
* @param {ModdleElement} source
* @param {ModdleElement} target
* @param {Array<string>} extensionElements
*/
_copyExtensionElements(source, target, extensionElements) {
const bpmnFactory = this._bpmnFactory;
// No extension elements in the source business object.
if (!source.extensionElements || !source.extensionElements.values) return;
const newExtensionElements = source.extensionElements.values.filter(value =>
extensionElements.some(extensionElement => ModelUtil.is(value, extensionElement))
);
// Nothing to copy.
if (!newExtensionElements.length) return;
target.extensionElements = bpmnFactory.create('bpmn:ExtensionElements', { values: newExtensionElements });
}
};
RemoveElementTemplateHandler$1.$inject = [
'modeling',
'elementFactory',
'elementRegistry',
'canvas',
'bpmnFactory',
'replace',
'commandStack',
'moddleCopy'
];
/**
* A handler that combines and executes multiple commands.
*
* All updates are bundled on the command stack and executed in one step.
* This also makes it possible to revert the changes in one step.
*
* Example use case: remove the camunda:formKey attribute and in addition
* add all form fields needed for the camunda:formData property.
*/
class MultiCommandHandler {
constructor(commandStack) {
this._commandStack = commandStack;
}
preExecute(context) {
const commandStack = this._commandStack;
minDash.forEach(context, function(command) {
commandStack.execute(command.cmd, command.context);
});
}
}
MultiCommandHandler.$inject = [ 'commandStack' ];
let ElementTemplatesCommands$1 = class ElementTemplatesCommands {
constructor(commandStack, elementTemplates, eventBus) {
commandStack.registerHandler(
'element-templates.multi-command-executor',
MultiCommandHandler
);
commandStack.registerHandler(
'propertiesPanel.zeebe.changeTemplate',
ChangeElementTemplateHandler$1
);
commandStack.registerHandler(
'propertiesPanel.removeTemplate',
RemoveElementTemplateHandler$1
);
// apply default element templates on shape creation
eventBus.on([ 'commandStack.shape.create.postExecuted' ], function(event) {
const {
context: {
hints = {},
shape
}
} = event;
if (hints.createElementsBehavior !== false) {
applyDefaultTemplate$1(shape, elementTemplates, commandStack);
}
});
// apply default element templates on connection creation
eventBus.on([ 'commandStack.connection.create.postExecuted' ], function(event) {
const {
context: {
hints = {},
connection
}
} = event;
if (hints.createElementsBehavior !== false) {
applyDefaultTemplate$1(connection, elementTemplates, commandStack);
}
});
}
};
ElementTemplatesCommands$1.$inject = [ 'commandStack', 'elementTemplates', 'eventBus' ];
function applyDefaultTemplate$1(element, elementTemplates, commandStack) {
if (!elementTemplates.get(element) && elementTemplates.getDefault(element)) {
const command = 'propertiesPanel.zeebe.changeTemplate';
const commandContext = {
element: element,
newTemplate: elementTemplates.getDefault(element)
};
commandStack.execute(command, commandContext);
}
}
var commandsModule$1 = {
__init__: [ 'elementTemplateCommands' ],
elementTemplateCommands: [ 'type', ElementTemplatesCommands$1 ]
};
class PropertyBindingProvider {
static create(element, options) {
const {
bpmnFactory,
property
} = options;
const {
binding
} = property;
const {
name
} = binding;
let value = getDefaultValue(property);
const businessObject = ModelUtil.getBusinessObject(element);
if (isExpression(businessObject, name)) {
const expression = createExpression(value, businessObject, bpmnFactory);
value = expression;
}
businessObject[name] = value;
}
}
class TaskDefinitionTypeBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const value = getDefaultValue(property);
const propertyName = getTaskDefinitionPropertyName(property.binding);
const taskDefinition = ensureExtension(element, 'zeebe:TaskDefinition', bpmnFactory);
taskDefinition.set(propertyName, value);
}
}
class InputBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const value = getDefaultValue(property);
const ioMapping = ensureExtension(element, 'zeebe:IoMapping', bpmnFactory);
if (!shouldUpdate(value, property)) {
return;
}
const input = createInputParameter$1(binding, value, bpmnFactory);
input.$parent = ioMapping;
ioMapping.get('inputParameters').push(input);
}
}
class OutputBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const value = getDefaultValue(property);
const ioMapping = ensureExtension(element, 'zeebe:IoMapping', bpmnFactory);
if (!shouldUpdate(value, property)) {
return;
}
const output = createOutputParameter$1(binding, value, bpmnFactory);
output.$parent = ioMapping;
ioMapping.get('outputParameters').push(output);
}
}
class TaskHeaderBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const value = getDefaultValue(property);
const taskHeaders = ensureExtension(element, 'zeebe:TaskHeaders', bpmnFactory);
const header = createTaskHeader(binding, value, bpmnFactory);
header.$parent = taskHeaders;
taskHeaders.get('values').push(header);
}
}
/**
* A provider that sets up the `zeebe:property` binding
* on a newly created moddle element.
*/
class ZeebePropertiesProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding,
} = property;
const value = getDefaultValue(property);
const zeebeProperties = ensureExtension(element, 'zeebe:Properties', bpmnFactory);
if (!shouldUpdate(value, property)) {
return;
}
const zeebeProperty = createZeebeProperty(binding, value, bpmnFactory);
zeebeProperty.$parent = zeebeProperties;
zeebeProperties.get('properties').push(zeebeProperty);
}
}
class MessagePropertyBindingProvider {
static create(element, options) {
const {
bpmnFactory,
property
} = options;
const {
binding
} = property;
const {
name
} = binding;
const value = getDefaultValue(property);
let businessObject = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(businessObject, 'bpmn:Event')) {
businessObject = businessObject.get('eventDefinitions')[0];
}
let message = businessObject.get('messageRef');
if (!message) {
message = bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': getTemplateId$1(element) });
businessObject.set('messageRef', message);
}
message.set(name, value);
}
}
class MessageZeebeSubscriptionBindingProvider {
static create(element, options) {
const {
bpmnFactory,
property
} = options;
const {
binding
} = property;
const {
name
} = binding;
const value = getDefaultValue(property);
let businessObject = ModelUtil.getBusinessObject(element);
if (ModelUtil.is(businessObject, 'bpmn:Event')) {
businessObject = businessObject.get('eventDefinitions')[0];
}
let message = businessObject.get('messageRef');
if (!message) {
message = bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': getTemplateId$1(element) });
businessObject.set('messageRef', message);
}
const subscription = ensureExtension(message, 'zeebe:Subscription', bpmnFactory);
if (!shouldUpdate(value, property)) {
return;
}
subscription.set(name, value);
}
}
class CalledElementBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const {
property: propertyName
} = binding;
const value = getDefaultValue(property);
const calledElement = ensureExtension(element, 'zeebe:CalledElement', bpmnFactory);
// TODO(@barmac): remove if we decide to support propagation in templates
ensureNoPropagation(calledElement);
calledElement.set(propertyName, value);
}
}
function ensureNoPropagation(calledElement) {
calledElement.set('propagateAllChildVariables', false);
calledElement.set('propagateAllParentVariables', false);
}
class LinkedResourcePropertyBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding: {
property: bindingProperty,
linkName
}
} = property;
const value = getDefaultValue(property);
const bo = ModelUtil.getBusinessObject(element);
const linkedResources = ensureExtension(element, 'zeebe:LinkedResources', bpmnFactory);
let linkedResource = linkedResources.get('values').find(linkedResource => linkedResource.get('linkName') === linkName);
if (!linkedResource) {
linkedResource = createElement('zeebe:LinkedResource', { linkName }, bo, bpmnFactory);
linkedResources.get('values').push(linkedResource);
}
linkedResource.set(bindingProperty, value);
}
}
class ZeebeUserTaskBindingProvider {
static create(element, options) {
const {
bpmnFactory
} = options;
ensureExtension(element, 'zeebe:UserTask', bpmnFactory);
}
}
class CalledDecisionBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const {
property: propertyName
} = binding;
const value = getDefaultValue(property);
const calledDecision = ensureExtension(element, 'zeebe:CalledDecision', bpmnFactory);
calledDecision.set(propertyName, value);
}
}
class ScriptTaskBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const {
property: propertyName
} = binding;
const value = getDefaultValue(property);
const scriptTask = ensureExtension(element, 'zeebe:Script', bpmnFactory);
scriptTask.set(propertyName, value);
}
}
class ZeebeFormDefinitionBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const {
property: propertyName
} = binding;
const value = getDefaultValue(property);
const formDefinition = ensureExtension(element, 'zeebe:FormDefinition', bpmnFactory);
formDefinition.set(propertyName, value);
}
}
class AssignmentDefinitionBindingProvider {
static create(element, options) {
const {
property,
bpmnFactory
} = options;
const {
binding
} = property;
const {
property: propertyName
} = binding;
const value = getDefaultValue(property);
const assignmentDefinition = ensureExtension(element, 'zeebe:AssignmentDefinition', bpmnFactory);
assignmentDefinition.set(propertyName, value);
}
}
const EXPRESSION_TYPES = [
'bpmn:Expression',
'bpmn:FormalExpression'
];
function getPropertyValue(element, property) {
const rawValue = getRawPropertyValue(element, property);
const { type } = property;
if (type === 'Boolean') {
return getBooleanPropertyValue(rawValue);
}
return rawValue;
}
function getRawPropertyValue(element, property) {
let businessObject = ModelUtil.getBusinessObject(element);
const defaultValue = '';
const {
binding
} = property;
const {
name,
property: bindingProperty,
type,
linkName
} = binding;
// property
if (type === 'property') {
const value = isExpression(element, name) ? getExpressionValue(element, name) : businessObject.get(name);
if (!minDash.isUndefined(value)) {
return value;
}
return defaultValue;
}
// zeebe:taskDefinition
if (TASK_DEFINITION_TYPES.includes(type)) {
const taskDefinition = findExtension$1(businessObject, 'zeebe:TaskDefinition');
if (taskDefinition) {
if (type === ZEEBE_TASK_DEFINITION_TYPE_TYPE) {
return taskDefinition.get('type');
} else if (type === ZEEBE_TASK_DEFINITION) {
return taskDefinition.get(bindingProperty);
}
}
return defaultValue;
}
if (IO_BINDING_TYPES.includes(type)) {
const ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');
if (!ioMapping) {
return defaultValue;
}
// zeebe:Input
if (type === ZEBBE_INPUT_TYPE) {
const inputParameter = findInputParameter(ioMapping, binding);
if (inputParameter) {
return inputParameter.get('source');
}
return defaultValue;
}
// zeebe:Output
if (type === ZEEBE_OUTPUT_TYPE) {
const outputParameter = findOutputParameter(ioMapping, binding);
if (outputParameter) {
return outputParameter.get('target');
}
return defaultValue;
}
}
// zeebe:taskHeaders
if (type === ZEEBE_TASK_HEADER_TYPE) {
const taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');
if (!taskHeaders) {
return defaultValue;
}
const header = findTaskHeader(taskHeaders, binding);
if (header) {
return header.get('value');
}
return defaultValue;
}
// zeebe:Property
if (type === ZEEBE_PROPERTY_TYPE) {
const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
if (zeebeProperties) {
const zeebeProperty = findZeebeProperty(zeebeProperties, binding);
if (zeebeProperty) {
return zeebeProperty.get('value');
}
}
return defaultValue;
}
// bpmn:Message#property
if (type === MESSAGE_PROPERTY_TYPE) {
const message = findMessage(businessObject);
const value = message ? message.get(name) : undefined;
if (!minDash.isUndefined(value)) {
return value;
}
return defaultValue;
}
// bpmn:Message#zeebe:subscription#property
if (type === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
const message = findMessage(businessObject);
if (message) {
const subscription = findExtension$1(message, 'zeebe:Subscription');
const value = subscription ? subscription.get(name) : undefined;
if (!minDash.isUndefined(value)) {
return subscription.get(name);
}
}
return defaultValue;
}
// zeebe:calledElement
if (type === ZEEBE_CALLED_ELEMENT) {
const calledElement = findExtension$1(businessObject, 'zeebe:CalledElement');
return calledElement ? calledElement.get(bindingProperty) : defaultValue;
}
if (type === ZEEBE_LINKED_RESOURCE_PROPERTY) {
const linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
if (!linkedResources) {
return defaultValue;
}
const linkedResource = linkedResources.get('values').find((value) => value.get('linkName') === linkName);
return linkedResource ? linkedResource.get(bindingProperty) : defaultValue;
}
// zeebe:userTask
if (type === ZEEBE_USER_TASK) {
const userTask = findExtension$1(businessObject, 'zeebe:userTask');
return userTask ? userTask.get(bindingProperty) : defaultValue;
}
// zeebe:calledDecision
if (type === ZEEBE_CALLED_DECISION) {
const calledDecision = findExtension$1(businessObject, 'zeebe:CalledDecision');
return calledDecision ? calledDecision.get(bindingProperty) : defaultValue;
}
// zeebe:formDefinition
if (type === ZEEBE_FORM_DEFINITION) {
const formDefinition = findExtension$1(businessObject, 'zeebe:FormDefinition');
return formDefinition ? formDefinition.get(bindingProperty) : defaultValue;
}
// zeebe:script
if (type === ZEEBE_SCRIPT_TASK) {
const scriptTask = findExtension$1(businessObject, 'zeebe:Script');
return scriptTask ? scriptTask.get(bindingProperty) : defaultValue;
}
if (type === ZEEBE_ASSIGNMENT_DEFINITION) {
const assignmentDefinition = findExtension$1(businessObject, 'zeebe:AssignmentDefinition');
return assignmentDefinition ? assignmentDefinition.get(bindingProperty) : defaultValue;
}
// should never throw as templates are validated beforehand
throw unknownBindingError(element, property);
}
/**
* Cast a string value to a boolean if possible. Otherwise return the value.
* Cannot always cast due to FEEL expressions.
*
* @param {string|boolean} value
*/
function getBooleanPropertyValue(value) {
switch (value) {
case 'true':
return true;
case 'false':
return false;
}
return value;
}
const NO_OP = null;
function setPropertyValue(bpmnFactory, commandStack, element, property, value) {
let businessObject = ModelUtil.getBusinessObject(element);
const {
binding,
} = property;
const {
name,
type,
property: bindingProperty,
linkName
} = binding;
let extensionElements;
let propertyValue;
const commands = [];
const context = {
element,
property
};
// ensure message exists
if (MESSAGE_BINDING_TYPES.includes(type)) {
if (ModelUtil.is(businessObject, 'bpmn:Event')) {
businessObject = businessObject.get('eventDefinitions')[0];
}
let message = findMessage(businessObject);
if (!message) {
message = bpmnFactory.create('bpmn:Message', { 'zeebe:modelerTemplate': getTemplateId$1(element) });
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: businessObject,
properties: { messageRef: message }
}
});
}
businessObject = message;
}
// ensure extension elements
if (EXTENSION_BINDING_TYPES.includes(type)) {
extensionElements = businessObject.get('extensionElements');
if (!extensionElements) {
extensionElements = createElement('bpmn:ExtensionElements', null, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: businessObject,
properties: { extensionElements }
}
});
} else {
commands.push(NO_OP);
}
}
// property
if (PROPERTY_BINDING_TYPES.includes(type)) {
const propertyType = businessObject.$descriptor.propertiesByName[ name ]?.type;
let propertyName = name;
if (!propertyType || propertyType === 'String') {
// make sure we create and don't remove the property
propertyValue = value || '';
} else if (propertyType === 'Boolean') {
propertyValue = !!value;
} else if (propertyType === 'Integer') {
propertyValue = parseInt(value, 10);
if (isNaN(propertyValue)) {
// do not set NaN value
propertyValue = undefined;
}
} else if (EXPRESSION_TYPES.includes(propertyType)) {
const existingExpression = businessObject.get(name);
if (existingExpression && ModelUtil.is(existingExpression, 'bpmn:FormalExpression')) {
// re-use existing expression
businessObject = existingExpression;
propertyName = 'body';
propertyValue = value || '';
} else {
propertyValue = createExpression(value, businessObject, bpmnFactory);
}
} else {
// unsupported non-primitive types cannot be set
throw new Error(`cannot set property of type <${ propertyType }>`);
}
if (!minDash.isUndefined(propertyValue)) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: businessObject,
properties: { [ propertyName ]: propertyValue }
}
});
} else {
commands.push(NO_OP);
}
}
// zeebe:taskDefinition
if (TASK_DEFINITION_TYPES.includes(type)) {
const oldTaskDefinition = findExtension$1(extensionElements, 'zeebe:TaskDefinition'),
propertyName = getTaskDefinitionPropertyName(binding),
properties = {
[ propertyName ]: value || ''
};
if (oldTaskDefinition) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
properties,
moddleElement: oldTaskDefinition
}
});
} else {
const newTaskDefinition = createTaskDefinition(properties, bpmnFactory);
newTaskDefinition.$parent = businessObject;
const values = extensionElements.get('values');
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...values, newTaskDefinition ] }
}
});
}
}
if (IO_BINDING_TYPES.includes(type)) {
let ioMapping = findExtension$1(extensionElements, 'zeebe:IoMapping');
if (!ioMapping) {
ioMapping = createElement('zeebe:IoMapping', null, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), ioMapping ] }
}
});
}
// zeebe:Input
if (type === ZEBBE_INPUT_TYPE) {
const oldZeebeInputParameter = findInputParameter(ioMapping, binding);
const values = ioMapping.get('inputParameters').filter((value) => value !== oldZeebeInputParameter);
// do not persist empty parameters when configured as <optional>
if (shouldUpdate(value, property)) {
const newZeebeInputParameter = createInputParameter$1(binding, value, bpmnFactory);
values.push(newZeebeInputParameter);
}
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: ioMapping,
properties: { inputParameters: [ ...values ] }
}
});
}
// zeebe:Output
if (type === ZEEBE_OUTPUT_TYPE) {
const oldZeebeOutputParameter = findOutputParameter(ioMapping, binding);
const values = ioMapping.get('outputParameters').filter((value) => value !== oldZeebeOutputParameter);
// do not persist empty parameters when configured as <optional>
if (shouldUpdate(value, property)) {
const newZeebeOutputParameter = createOutputParameter$1(binding, value, bpmnFactory);
values.push(newZeebeOutputParameter);
}
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: ioMapping,
properties: { 'outputParameters': [ ...values ] }
}
});
}
}
// zeebe:taskHeaders
if (type === ZEEBE_TASK_HEADER_TYPE) {
let taskHeaders = findExtension$1(extensionElements, 'zeebe:TaskHeaders');
if (!taskHeaders) {
taskHeaders = createElement('zeebe:TaskHeaders', null, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), taskHeaders ] }
}
});
}
const oldTaskHeader = findTaskHeader(taskHeaders, binding);
const values = taskHeaders.get('values').filter((value) => value !== oldTaskHeader);
// do not persist task headers with empty value
if (!value) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: taskHeaders,
properties: { values }
}
});
} else {
const newTaskHeader = createTaskHeader(binding, value, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: taskHeaders,
properties: { values: [ ...values, newTaskHeader ] }
}
});
}
}
// zeebe:Property
if (type === ZEEBE_PROPERTY_TYPE) {
let zeebeProperties = findExtension$1(extensionElements, 'zeebe:Properties');
if (!zeebeProperties) {
zeebeProperties = createElement('zeebe:Properties', null, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), zeebeProperties ]
}
}
});
}
const oldZeebeProperty = findZeebeProperty(zeebeProperties, binding);
const properties = zeebeProperties.get('properties').filter((property) => property !== oldZeebeProperty);
if (shouldUpdate(value, property)) {
const newZeebeProperty = createZeebeProperty(binding, value, bpmnFactory);
properties.push(newZeebeProperty);
}
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: zeebeProperties,
properties: {
properties
}
}
});
}
// bpmn:Message#zeebe:subscription#property
if (type === MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE) {
let subscription = findExtension$1(extensionElements, 'zeebe:Subscription');
const properties = {
[ name ]: value || ''
};
if (subscription) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: subscription
}
});
} else {
subscription = createElement('zeebe:Subscription', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), subscription ] }
}
});
}
}
// zeebe:calledElement
if (type === ZEEBE_CALLED_ELEMENT) {
let calledElement = findExtension$1(element, 'zeebe:CalledElement');
const propertyName = binding.property;
const properties = {
[ propertyName ]: value || ''
};
if (calledElement) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: calledElement
}
});
} else {
calledElement = createElement('zeebe:CalledElement', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), calledElement ] }
}
});
}
}
if (type === ZEEBE_LINKED_RESOURCE_PROPERTY) {
let linkedResources = findExtension$1(businessObject, 'zeebe:LinkedResources');
if (!linkedResources) {
linkedResources = createElement('zeebe:LinkedResources', null, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), linkedResources ] }
}
});
}
let linkedResource = linkedResources.get('values').find((value) => value.get('linkName') === linkName);
if (!linkedResource) {
linkedResource = createElement('zeebe:LinkedResource', { linkName }, businessObject, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: linkedResources,
properties: { values: [ ...linkedResources.get('values'), linkedResource ] }
}
});
}
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: linkedResource,
properties: { [ bindingProperty ]: value }
}
});
}
// zeebe:calledDecision
if (type === ZEEBE_CALLED_DECISION) {
let calledDecision = findExtension$1(element, 'zeebe:CalledDecision');
const propertyName = binding.property;
const properties = {
[ propertyName ]: value || ''
};
if (calledDecision) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: calledDecision
}
});
} else {
calledDecision = createElement('zeebe:CalledDecision', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), calledDecision ] }
}
});
}
}
// zeebe:script
if (type === ZEEBE_SCRIPT_TASK) {
let scriptTask = findExtension$1(element, 'zeebe:Script');
const propertyName = binding.property;
const properties = {
[ propertyName ]: value || ''
};
if (scriptTask) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: scriptTask
}
});
} else {
scriptTask = createElement('zeebe:Script', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), scriptTask ] }
}
});
}
}
if (type === ZEEBE_FORM_DEFINITION) {
let formDefinition = findExtension$1(element, 'zeebe:FormDefinition');
const propertyName = binding.property;
const properties = {
[ propertyName ]: value || ''
};
if (formDefinition) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: formDefinition
}
});
} else {
formDefinition = createElement('zeebe:FormDefinition', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), formDefinition ] }
}
});
}
}
// zeebe:assignmentDefinition
if (type === ZEEBE_ASSIGNMENT_DEFINITION) {
let assignmentDefinition = findExtension$1(element, 'zeebe:AssignmentDefinition');
const propertyName = binding.property;
const properties = {
[ propertyName ]: value || ''
};
if (assignmentDefinition) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
properties,
moddleElement: assignmentDefinition
}
});
} else {
assignmentDefinition = createElement('zeebe:AssignmentDefinition', properties, extensionElements, bpmnFactory);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: extensionElements,
properties: { values: [ ...extensionElements.get('values'), assignmentDefinition ] }
}
});
}
}
if (commands.length) {
const commandsToExecute = commands.filter((command) => command !== NO_OP);
commandsToExecute.length && commandStack.execute(
'element-templates.multi-command-executor',
commandsToExecute
);
return;
}
// should never throw as templates are validated beforehand
throw unknownBindingError(element, property);
}
// helpers
function unknownBindingError(element, property) {
const businessObject = ModelUtil.getBusinessObject(element);
const id = businessObject.get('id');
const { binding } = property;
const { type } = binding;
return new Error(`unknown binding <${ type }> for element <${ id }>, this should never happen`);
}
/**
* Based on conditions, remove properties from the template.
*/
function applyConditions(element, elementTemplate) {
const { properties } = elementTemplate;
const filteredProperties = properties.filter(property => {
return isPropertyAllowed(element, property) && isConditionMet(element, properties, property);
});
return {
...elementTemplate,
properties: filteredProperties
};
}
function isConditionMet(element, properties, property) {
const { condition } = property;
// If no condition is defined, return true.
if (!condition) {
return true;
}
// multiple ("and") conditions
if (condition.allMatch) {
const conditions = condition.allMatch;
return conditions.every(
condition => isSimpleConditionMet(element, properties, condition)
);
}
// single condition
return isSimpleConditionMet(element, properties, condition);
}
function isSimpleConditionMet(element, properties, condition) {
const { property, equals, oneOf, isActive } = condition;
if (typeof isActive !== 'undefined') {
const relatedCondition = properties.find(p => p.id === property);
if (!relatedCondition) {
return !isActive;
}
return isActive ? isConditionMet(element, properties, relatedCondition) : !isConditionMet(element, properties, relatedCondition);
}
const propertyObject = getProperty(properties, property);
if (propertyObject) {
// if the property is a FEEL [optional, static, required] property,
// we need to get the value removing the '='
const propertyValue = shouldCastToFeel(propertyObject) || propertyObject.feel === 'required' ?
getPropertyValue(element, propertyObject).slice(1) :
getPropertyValue(element, propertyObject);
if (hasProperty(condition, 'equals')) {
return compareProperty(propertyObject, propertyValue, equals);
}
if (oneOf) {
return oneOf.includes(propertyValue);
}
}
return false;
}
function getProperty(properties, propertyId) {
const property = properties.find(p => p.id === propertyId);
if (!property) {
return;
}
return property;
}
function isPropertyAllowed(element, property) {
const { binding } = property;
const { type } = binding;
if (
type === 'bpmn:Message#zeebe:subscription#property' &&
binding.name === 'correlationKey' &&
ModelUtil.is(element, 'bpmn:StartEvent') &&
!DiUtil.isEventSubProcess(element.parent)
) {
return false;
}
return true;
}
function compareProperty(propertyObj, propertyValue, value) {
// Number
if (propertyObj.type === 'Number') {
return Number(propertyValue) === value;
}
// Boolean
else if (propertyObj.type === 'Boolean') {
return Boolean(propertyValue) === value;
}
return propertyValue === value;
}
// helpers //////////////////////
function hasProperty(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
class TemplateElementFactory {
constructor(bpmnFactory, elementFactory) {
this._bpmnFactory = bpmnFactory;
this._elementFactory = elementFactory;
this._providers = {
[PROPERTY_TYPE]: PropertyBindingProvider,
[ZEEBE_TASK_DEFINITION_TYPE_TYPE]: TaskDefinitionTypeBindingProvider,
[ZEEBE_TASK_DEFINITION]: TaskDefinitionTypeBindingProvider,
[ZEBBE_PROPERTY_TYPE]: ZeebePropertiesProvider,
[ZEBBE_INPUT_TYPE]: InputBindingProvider,
[ZEEBE_OUTPUT_TYPE]: OutputBindingProvider,
[ZEEBE_TASK_HEADER_TYPE]: TaskHeaderBindingProvider,
[MESSAGE_PROPERTY_TYPE]: MessagePropertyBindingProvider,
[MESSAGE_ZEEBE_SUBSCRIPTION_PROPERTY_TYPE]: MessageZeebeSubscriptionBindingProvider,
[ZEEBE_CALLED_ELEMENT]: CalledElementBindingProvider,
[ZEEBE_LINKED_RESOURCE_PROPERTY]: LinkedResourcePropertyBindingProvider,
[ZEEBE_USER_TASK]: ZeebeUserTaskBindingProvider,
[ZEEBE_CALLED_DECISION]: CalledDecisionBindingProvider,
[ZEEBE_FORM_DEFINITION]: ZeebeFormDefinitionBindingProvider,
[ZEEBE_SCRIPT_TASK]: ScriptTaskBindingProvider,
[ZEEBE_ASSIGNMENT_DEFINITION]: AssignmentDefinitionBindingProvider
};
}
/**
* Create an element based on an element template.
*
* @param {ElementTemplate} template
* @returns {djs.model.Base}
*/
create(template) {
const {
properties
} = template;
// (1) base shape
const element = this._createShape(template);
// (2) apply template
this._setModelerTemplate(element, template);
// (3) apply icon
if (hasIcon(template)) {
this._setModelerTemplateIcon(element, template);
}
// (4) apply properties
this._applyProperties(element, properties);
return element;
}
_createShape(template) {
const {
appliesTo,
elementType = {}
} = template;
const elementFactory = this._elementFactory;
const attrs = {
type: elementType.value || appliesTo[0]
};
// apply eventDefinition
if (elementType.eventDefinition) {
attrs.eventDefinitionType = elementType.eventDefinition;
}
const element = elementFactory.createShape(attrs);
return element;
}
_ensureExtensionElements(element) {
const bpmnFactory = this._bpmnFactory;
const businessObject = ModelUtil.getBusinessObject(element);
let extensionElements = businessObject.get('extensionElements');
if (!extensionElements) {
extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
values: []
});
extensionElements.$parent = businessObject;
businessObject.set('extensionElements', extensionElements);
}
return extensionElements;
}
_setModelerTemplate(element, template) {
const {
id,
version
} = template;
const businessObject = ModelUtil.getBusinessObject(element);
businessObject.set('zeebe:modelerTemplate', id);
businessObject.set('zeebe:modelerTemplateVersion', version);
}
_setModelerTemplateIcon(element, template) {
const {
icon
} = template;
const {
contents
} = icon;
const businessObject = ModelUtil.getBusinessObject(element);
businessObject.set('zeebe:modelerTemplateIcon', contents);
}
/**
* Apply properties to a given element.
*
* @param {djs.model.Base} element
* @param {Array<Object>} properties
*/
_applyProperties(element, properties) {
const processedProperties = [];
properties.forEach(
property => this._applyProperty(element, property, properties, processedProperties)
);
}
/**
* Apply a property and its parent properties to an element based on conditions.
*
* @param {djs.model.Base} element
* @param {Object} property
* @param {Array<Object>} properties
* @param {Array<Object>} processedProperties
*/
_applyProperty(element, property, properties, processedProperties) {
// skip if already processed
if (processedProperties.includes(property)) {
return;
}
// apply dependant property first if not already applied
const dependentProperties = findDependentProperties(property, properties);
dependentProperties.forEach(
property => this._applyProperty(element, property, properties, processedProperties)
);
// check condition and apply property if condition is met
if (isConditionMet(element, properties, property)) {
this._bindProperty(property, element);
}
processedProperties.push(property);
}
/**
* Bind property to element.
* @param {Object} property
* @param {djs.Model.Base} element
*/
_bindProperty(property, element) {
const {
binding
} = property;
const {
type: bindingType
} = binding;
const bindingProvider = this._providers[bindingType];
bindingProvider.create(element, {
property,
bpmnFactory: this._bpmnFactory
});
}
}
TemplateElementFactory.$inject = [ 'bpmnFactory', 'elementFactory' ];
// helper ////////////////
function hasIcon(template) {
const {
icon
} = template;
return !!(icon && icon.contents);
}
function findDependentProperties(property, properties) {
const {
condition
} = property;
if (!condition) {
return [];
}
const dependentProperty = findProperyById(properties, condition.property);
if (dependentProperty) {
return [ dependentProperty ];
}
return [];
}
function findProperyById(properties, id) {
return minDash.find(properties, function(property) {
return property.id === id;
});
}
var createModule = {
__init__: [
'templateElementFactory'
],
templateElementFactory: [ 'type', TemplateElementFactory ]
};
/**
* Checks the conditions of an element template and sets/resets the
* corresponding properties on the element.
*/
class ConditionalBehavior extends CommandInterceptor {
/**
* @param {import('diagram-js/lib/core/EventBus').default} eventBus
* @param {import('../ElementTemplates').default} elementTemplates
* @param {import('diagram-js/lib/command/CommandStack').default} commandStack
* @param {import('bpmn-js/lib/features/modeling/BpmnFactory').default} bpmnFactory
* @param {import('didi').Injector} injector
*/
constructor(eventBus, elementTemplates, commandStack, bpmnFactory, injector) {
super(eventBus);
this._eventBus = eventBus;
this._elementTemplates = elementTemplates;
this._commandStack = commandStack;
this._bpmnFactory = bpmnFactory;
this._injector = injector;
// (1) save pre-conditional state before updating a property
this.preExecute([
'element.updateProperties',
'element.updateModdleProperties',
'element.move'
], this._saveConditionalState, true, this);
// (2) check if we need to apply post-conditional updates
//
// if [additional bindings activate] then
// (re-)trigger setting the template
// else
// else we're done
//
this.postExecute([
'element.updateProperties',
'element.updateModdleProperties',
'propertiesPanel.zeebe.changeTemplate',
'element.move'
], this._applyConditions, true, this);
// (3) set conditions before changing the template
this.preExecute([
'propertiesPanel.zeebe.changeTemplate'
], this._ensureConditional, true, this);
}
_saveConditionalState(context) {
const {
element
} = context;
const template = this._elementTemplates.get(element);
if (!template) {
return;
}
context.oldTemplateWithConditions = applyConditions(element, template);
}
_applyConditions(context, event) {
const {
element,
newTemplate,
oldTemplateWithConditions
} = context;
// do not apply conditions if we only update meta information
if (isMetaUpdate(event, context)) {
return;
}
const template = this._elementTemplates.get(element);
// new Template is persisted before applying default values,
// new conditions might apply after the defaults are present.
const oldTemplate = oldTemplateWithConditions || newTemplate;
if (!template || !oldTemplate) {
return;
}
const newTemplateWithConditions = applyConditions(element, template);
// verify that new bindings were activated
if (!hasDifferentPropertyBindings(newTemplateWithConditions, oldTemplate)) {
return;
}
// do another pass to apply further conditional bindings
// newTemplate will always be the original template; it is filtered
// at a later step (3)
const changeContext = {
element,
newTemplate: template,
oldTemplate
};
this._commandStack.execute('propertiesPanel.zeebe.changeTemplate', changeContext);
}
_ensureConditional(context) {
const {
element,
newTemplate
} = context;
if (!newTemplate) {
return;
}
// ensure conditions are applied before changing the template.
// `newTemplate` will always be the original template.
context.newTemplate = applyConditions(element, newTemplate);
}
}
ConditionalBehavior.$inject = [
'eventBus',
'elementTemplates',
'commandStack',
'bpmnFactory',
'injector'
];
// helpers
function hasDifferentPropertyBindings(sourceTemplate, targetTemplate) {
return hasNewProperties(sourceTemplate, targetTemplate) || hasRemovedProperties(sourceTemplate, targetTemplate);
}
function hasNewProperties(sourceTemplate, targetTemplate) {
let properties = targetTemplate.properties;
return properties.some(targetProp =>!(
sourceTemplate.properties.find(sourceProp => compareProps(sourceProp, targetProp))
));
}
function hasRemovedProperties(oldTemplate, newTemplate) {
const oldProperties = getMissingProperties(newTemplate, oldTemplate);
// ensure XML properties are mantained for properties with
// different conditions but same bindings
return oldProperties.some(property =>
!findPropertyWithBinding(newTemplate, property)
);
}
function getMissingProperties(sourceTemplate, targetTemplate) {
let properties = targetTemplate.properties;
return properties.filter(targetProp =>!(
sourceTemplate.properties.find(sourceProp => compareProps(sourceProp, targetProp))
));
}
function compareProps(sourceProp, targetProp) {
return (
areBindingsEqual(sourceProp.binding, targetProp.binding) &&
equals(sourceProp.condition, targetProp.condition)
);
}
function findPropertyWithBinding(template, prop1) {
return template.properties.some(
prop2 => areBindingsEqual(prop1.binding, prop2.binding)
);
}
function normalizeReplacer$1(key, value) {
if (minDash.isObject(value)) {
const keys = Object.keys(value).sort();
return keys.reduce((obj, key) => {
obj[key] = value[key];
return obj;
}, {});
}
return value;
}
function areBindingsEqual(binding1, binding2) {
binding1 = normalizeBinding(binding1);
binding2 = normalizeBinding(binding2);
return equals(binding1, binding2);
}
/**
* Convert deprecated binding type to new type.
*/
function normalizeBinding(binding) {
if (binding.type === ZEEBE_TASK_DEFINITION_TYPE_TYPE) {
return {
...binding,
type: ZEEBE_TASK_DEFINITION,
property: 'type'
};
}
return binding;
}
function equals(a, b) {
return JSON.stringify(a, normalizeReplacer$1) === JSON.stringify(b, normalizeReplacer$1);
}
/**
* Checks if the event only updates "meta" properties:
*
* * `zeebe:modelerTemplate`
* * `zeebe:modelerTemplateVersion`
*
* @param {string} event
* @param {any} context
*
* @return {boolean}
*/
function isMetaUpdate(event, context) {
return event === 'element.updateProperties' && (
Object.keys(context.properties).every(
key => [ 'zeebe:modelerTemplate', 'zeebe:modelerTemplateVersion' ].includes(key)
));
}
/**
* This Behavior checks if the new element's type is in
* the list of elements the template applies to and unlinks
* it if not.
*/
let ReplaceBehavior$1 = class ReplaceBehavior extends CommandInterceptor {
constructor(elementTemplates, injector) {
super(injector.get('eventBus'));
this.postExecuted('shape.replace', function(e) {
var context = e.context,
oldShape = context.oldShape,
oldBo = ModelUtil.getBusinessObject(oldShape),
newShape = context.newShape,
newBo = ModelUtil.getBusinessObject(newShape);
if (!oldBo.modelerTemplate) {
return;
}
const template = newBo.modelerTemplate;
const version = newBo.modelerTemplateVersion;
const elementTemplate = elementTemplates.get(template, version);
if (!elementTemplate) {
elementTemplates.unlinkTemplate(newShape);
return;
}
const { appliesTo, elementType } = elementTemplate;
if (elementType) {
if (!ModelUtil.is(newShape, elementType.value) || shouldUnlinkEvent(newShape, elementType)) {
elementTemplates.unlinkTemplate(newShape);
}
return;
}
const allowed = appliesTo.reduce((allowed, type) => {
return allowed || ModelUtil.is(newBo, type);
}, false);
if (!allowed) {
elementTemplates.unlinkTemplate(newShape);
}
});
}
};
ReplaceBehavior$1.$inject = [
'elementTemplates',
'injector'
];
function shouldUnlinkEvent(newShape, elementType) {
if (!ModelUtil.is(newShape, 'bpmn:Event')) {
return false;
}
const { eventDefinition } = elementType,
bo = ModelUtil.getBusinessObject(newShape),
eventDefinitions = bo.get('eventDefinitions');
if (!eventDefinition) {
return eventDefinitions.length !== 0;
}
return !ModelUtil.is(eventDefinitions[0], eventDefinition);
}
/**
* Restores the original order of the template properties
* on the moddle element.
*/
class UpdateTemplatePropertiesOrder extends CommandInterceptor {
constructor(eventBus, elementTemplates, commandStack, bpmnFactory) {
super(eventBus);
this._eventBus = eventBus;
this._elementTemplates = elementTemplates;
this._commandStack = commandStack;
this._bpmnFactory = bpmnFactory;
this.postExecute([
'element.updateProperties', 'element.updateModdleProperties'
], this._updatePropertiesOrder, true, this);
}
_updatePropertiesOrder(context) {
const {
element
} = context;
const template = this._elementTemplates.get(element);
const businessObject = ModelUtil.getBusinessObject(element);
const commands = [];
if (!template) {
return;
}
const templateProperties = applyConditions(element, template).properties;
// zeebe:Property
const zeebeProperties = findExtension$1(businessObject, 'zeebe:Properties');
if (zeebeProperties) {
this._updateZeebePropertiesOrder(zeebeProperties, templateProperties, commands, context);
}
// zeebe:IoMapping
const ioMapping = findExtension$1(businessObject, 'zeebe:IoMapping');
if (ioMapping) {
// zeebe:Input
this._updateInputOrder(ioMapping, templateProperties, commands, context);
// zeebe:Output
this._updateOutputOrder(ioMapping, templateProperties, commands, context);
}
// zeebe:TaskHeaders
const taskHeaders = findExtension$1(businessObject, 'zeebe:TaskHeaders');
if (taskHeaders) {
this._updateTaskHeadersOrder(taskHeaders, templateProperties, commands, context);
}
if (commands.length) {
const commandsToExecute = commands.filter((command) => command !== null);
commandsToExecute.length && this._commandStack.execute(
'element-templates.multi-command-executor',
commandsToExecute
);
return;
}
}
_updateZeebePropertiesOrder(zeebeProperties, templateProperties, commands, context) {
const findIndex = (properties, propertyToFind) =>
properties.findIndex(prop =>
prop.binding.type == 'zeebe:property' && prop.binding.name === propertyToFind.get('name')
);
const properties = zeebeProperties.get('properties');
if (properties.length < 1)
return;
let newPropertiesOrder = [ ...properties ];
sortProperties(newPropertiesOrder, findIndex, templateProperties);
if (!arrayEquals(newPropertiesOrder, properties)) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: zeebeProperties,
properties: {
properties: newPropertiesOrder
}
}
});
}
}
_updateInputOrder(ioMapping, templateProperties, commands, context) {
const findIndex = (properties, propertyToFind) =>
properties.findIndex(prop =>
prop.binding.type == 'zeebe:input' && prop.binding.name === propertyToFind.get('target')
);
const inputParameters = ioMapping.get('inputParameters');
if (inputParameters.length < 1)
return;
let newInputOrder = [ ...inputParameters ];
sortProperties(newInputOrder, findIndex, templateProperties);
if (!arrayEquals(newInputOrder,inputParameters)) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: ioMapping,
properties: { inputParameters: newInputOrder }
}
});
}
}
_updateOutputOrder(ioMapping, templateProperties, commands, context) {
const findIndex = (properties, propertyToFind) =>
properties.findIndex(prop =>
prop.binding.type == 'zeebe:output' && prop.binding.source === propertyToFind.get('source')
);
const outputParameters = ioMapping.get('outputParameters');
if (outputParameters.length < 1)
return;
let newOutputOrder = [ ...outputParameters ];
sortProperties(newOutputOrder, findIndex, templateProperties);
if (!arrayEquals(newOutputOrder, outputParameters)) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: ioMapping,
properties: { outputParameters: newOutputOrder }
}
});
}
}
_updateTaskHeadersOrder(taskHeaders, templateProperties, commands, context) {
const findIndex = (properties, propertyToFind) =>
properties.findIndex(prop =>
prop.binding.type == 'zeebe:taskHeader' && prop.binding.key === propertyToFind.get('key')
);
const headers = taskHeaders.get('zeebe:values');
if (headers.length < 1)
return;
let newHeadersOrder = [ ...headers ];
sortProperties(newHeadersOrder, findIndex, templateProperties);
if (!arrayEquals(newHeadersOrder, headers)) {
commands.push({
cmd: 'element.updateModdleProperties',
context: {
...context,
moddleElement: taskHeaders,
properties: { values: newHeadersOrder }
}
});
}
}
}
UpdateTemplatePropertiesOrder.$inject = [
'eventBus',
'elementTemplates',
'commandStack',
'bpmnFactory'
];
// helpers
function normalizeReplacer(key, value) {
if (minDash.isObject(value)) {
const keys = Object.keys(value).sort();
return keys.reduce((obj, key) => {
obj[key] = value[key];
return obj;
}, {});
}
return value;
}
function objectEquals(a, b) {
return JSON.stringify(a, normalizeReplacer) === JSON.stringify(b, normalizeReplacer);
}
function arrayEquals(a, b) {
return a.every((element, idx) => objectEquals(element, b[idx]));
}
function sortProperties(array, findIndex, templateProperties) {
return array.sort((a, b) => {
const aIndex = findIndex(templateProperties, a);
const bIndex = findIndex(templateProperties, b);
return aIndex - bIndex;
});
}
/**
* Handles referenced elements.
*/
class ReferencedElementBehavior extends CommandInterceptor {
constructor(eventBus, elementTemplates, modeling, injector, moddleCopy, bpmnFactory) {
super(eventBus);
this._eventBus = eventBus;
this._elementTemplates = elementTemplates;
this._modeling = modeling;
this._injector = injector;
this.postExecuted([
'element.updateProperties', 'element.updateModdleProperties'
], this._handlePropertiesUpdate, true, this);
this.postExecuted('shape.replace', this._handleReplacement, true, this);
this.postExecuted('shape.delete', this._handleRemoval, true, this);
// copy templated root element when pasting
eventBus.on('copyPaste.pasteElement', function(context) {
const {
referencedRootElement
} = context.descriptor;
if (!referencedRootElement) {
return;
}
if (!getTemplateId$1(referencedRootElement)) {
return;
}
context.descriptor.referencedRootElement = moddleCopy.copyElement(
referencedRootElement,
bpmnFactory.create(referencedRootElement.$type)
);
});
}
/**
* Unlink referenced element when template is unlinked.
*/
_handlePropertiesUpdate(context) {
const { element, properties } = context;
if (!canHaveReferencedElement(element)) {
return;
}
if (!(TEMPLATE_ID_ATTR$1 in properties) || minDash.isString(properties[TEMPLATE_ID_ATTR$1])) {
return;
}
const bo = ModelUtil.getBusinessObject(element);
const message = findMessage(bo);
if (message && getTemplateId$1(message)) {
this._modeling.updateModdleProperties(element, message, {
[TEMPLATE_ID_ATTR$1]: null
});
}
}
/**
* Remove referenced element when template is removed.
* Keep referenced element when template is replaced.
*/
_handleReplacement(context) {
const { oldShape, newShape } = context;
const oldTemplate = getTemplateId$1(oldShape),
newTemplate = getTemplateId$1(newShape);
if (!canHaveReferencedElement(oldShape) || !oldTemplate) {
return;
}
const bo = ModelUtil.getBusinessObject(oldShape);
const message = findMessage(bo);
if (!message || !getTemplateId$1(message)) {
return;
}
if (!canHaveReferencedElement(newShape) || !newTemplate) {
removeRootElement(message, this._injector);
return;
}
this._addMessage(newShape, message);
}
_handleRemoval(context) {
const { shape } = context;
if (isLabel(shape)) {
return;
}
if (!canHaveReferencedElement(shape)) {
return;
}
if (!getTemplateId$1(shape)) {
return;
}
const bo = ModelUtil.getBusinessObject(shape);
const message = findMessage(bo);
if (message && getTemplateId$1(message)) {
removeRootElement(message, this._injector);
}
}
_addMessage(element, message) {
const bo = getReferringElement(element);
this._modeling.updateModdleProperties(element, bo, {
'messageRef': message
});
}
}
ReferencedElementBehavior.$inject = [
'eventBus',
'elementTemplates',
'modeling',
'injector',
'moddleCopy',
'bpmnFactory'
];
function canHaveReferencedElement(element) {
// Blank-Events can't have referenced elements
if (ModelUtil.is(element, 'bpmn:Event')) {
const bo = ModelUtil.getBusinessObject(element);
return bo.get('eventDefinitions') && bo.get('eventDefinitions')[0];
}
return ModelUtil.isAny(element, [
'bpmn:ReceiveTask',
'bpmn:SendTask'
]);
}
function isLabel(element) {
return element.type === 'label';
}
/**
* Handles generated value properties.
*/
class GeneratedValueBehavior extends CommandInterceptor {
constructor(eventBus, elementTemplates, modeling, commandStack, bpmnFactory) {
super(eventBus);
this._eventBus = eventBus;
this._elementTemplates = elementTemplates;
this._modeling = modeling;
this.preExecute('shape.create', context => {
const element = context.shape;
const template = elementTemplates.get(element);
if (!template) {
return;
}
const generatedProps = template.properties.filter(p => p.generatedValue);
generatedProps.forEach(p => {
if (!getPropertyValue(element, p)) {
return;
}
const value = getDefaultValue(p);
setPropertyValue(bpmnFactory, commandStack, element, p, value);
});
}, true);
}
}
GeneratedValueBehavior.$inject = [
'eventBus',
'elementTemplates',
'modeling',
'commandStack',
'bpmnFactory'
];
/**
* Enforces no variable propagation for templated call activities.
*/
class CalledElementBehavior extends CommandInterceptor {
/**
* @param {*} eventBus
* @param {*} modeling
* @param {import('../ElementTemplates').default} elementTemplates
*/
constructor(eventBus, modeling, elementTemplates) {
super(eventBus);
this._modeling = modeling;
this._elementTemplates = elementTemplates;
this.postExecuted([
'element.updateProperties', 'element.updateModdleProperties'
], this._ensureNoPropagation, true, this);
}
_ensureNoPropagation(context) {
const { element } = context;
if (!this._elementTemplates.get(element)) {
return;
}
if (!ModelUtil.is(element, 'bpmn:CallActivity')) {
return;
}
const calledElement = findExtension$1(element, 'zeebe:CalledElement');
if (!calledElement) {
return;
}
for (const property of [
'propagateAllChildVariables',
'propagateAllParentVariables'
]) {
if (calledElement.get(property) !== false) {
this._modeling.updateModdleProperties(element, calledElement, {
[property]: false
});
}
}
}
}
CalledElementBehavior.$inject = [
'eventBus',
'modeling',
'elementTemplates'
];
/**
* Disable the default Camunda behavior for a newly created element
* if it is a templated `<bpmn:UserTask>`.
*/
class UserTaskBehavior extends CommandInterceptor {
constructor(eventBus, elementTemplates) {
super(eventBus);
this.preExecute('shape.create', function(event) {
const {
context: {
shape,
hints = {},
}
} = event;
if (ModelUtil.is(shape, 'bpmn:UserTask') && elementTemplates.get(shape)) {
minDash.assign(hints, {
createElementsBehavior: false
});
}
});
}
}
UserTaskBehavior.$inject = [
'eventBus',
'elementTemplates'
];
var behaviorModule$1 = {
__init__: [
'elementTemplatesReplaceBehavior',
'elementTemplatesConditionalBehavior',
'elementTemplatesGeneratedValueBehavior',
'elementTemplatesReferencedElementBehavior',
'elementTemplatesUpdatePropertiesOrderBehavior',
'elementTemplatesCalledElementBehavior',
'elementTemplatesUserTaskBehavior'
],
elementTemplatesReplaceBehavior: [ 'type', ReplaceBehavior$1 ],
elementTemplatesConditionalBehavior: [ 'type', ConditionalBehavior ],
elementTemplatesGeneratedValueBehavior: [ 'type', GeneratedValueBehavior ],
elementTemplatesReferencedElementBehavior: [ 'type', ReferencedElementBehavior ],
elementTemplatesUpdatePropertiesOrderBehavior: [ 'type', UpdateTemplatePropertiesOrder ],
elementTemplatesCalledElementBehavior: [ 'type', CalledElementBehavior ],
elementTemplatesUserTaskBehavior: [ 'type', UserTaskBehavior ]
};
var index$1 = {
__depends__: [
commandsModule$1,
behaviorModule$1,
createModule
],
__init__: [
'elementTemplatesLoader'
],
elementTemplates: [ 'type', ElementTemplates ],
elementTemplatesLoader: [ 'type', ElementTemplatesLoader ]
};
/**
* Converts legacy scopes descriptor to newer supported array structure.
*
* For example, it transforms
*
* scopes: {
* 'camunda:Connector':
* { properties: []
* }
* }
*
* to
*
* scopes: [
* {
* type: 'camunda:Connector',
* properties: []
* }
* ]
*
* @param {ScopesDescriptor} scopes
*
* @returns {Array}
*/
function handleLegacyScopes(scopes = []) {
const scopesAsArray = [];
if (!minDash.isObject(scopes)) {
return scopes;
}
minDash.forEach(minDash.keys(scopes), function(scopeName) {
scopesAsArray.push(minDash.assign({
type: scopeName
}, scopes[scopeName]));
});
return scopesAsArray;
}
/**
* Create an input parameter representing the given
* binding and value.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createInputParameter(binding, value, bpmnFactory) {
const {
name,
scriptFormat
} = binding;
let parameterValue,
parameterDefinition;
if (scriptFormat) {
parameterDefinition = bpmnFactory.create('camunda:Script', {
scriptFormat,
value
});
} else {
parameterValue = value;
}
return bpmnFactory.create('camunda:InputParameter', {
name,
value: parameterValue,
definition: parameterDefinition
});
}
/**
* Create an output parameter representing the given
* binding and value.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createOutputParameter(binding, value, bpmnFactory) {
const {
scriptFormat,
source
} = binding;
let parameterValue,
parameterDefinition;
if (scriptFormat) {
parameterDefinition = bpmnFactory.create('camunda:Script', {
scriptFormat,
value: source
});
} else {
parameterValue = source;
}
return bpmnFactory.create('camunda:OutputParameter', {
name: value,
value: parameterValue,
definition: parameterDefinition
});
}
/**
* Create camunda property from the given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaProperty(binding, value = '', bpmnFactory) {
const { name } = binding;
return bpmnFactory.create('camunda:Property', {
name,
value
});
}
/**
* Create camunda:in element from given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaIn(binding, value, bpmnFactory) {
const attrs = createCamundaInOutAttrs(binding, value);
return bpmnFactory.create('camunda:In', attrs);
}
/**
* Create camunda:in with businessKey element from given binding.
*
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaInWithBusinessKey(value, bpmnFactory) {
return bpmnFactory.create('camunda:In', {
businessKey: value
});
}
/**
* Create camunda:out element from given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaOut(binding, value, bpmnFactory) {
const attrs = createCamundaInOutAttrs(binding, value);
return bpmnFactory.create('camunda:Out', attrs);
}
/**
* Create camunda:executionListener element containing an inline script from given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaExecutionListener(binding, value, bpmnFactory) {
const {
event,
implementationType,
scriptFormat
} = binding;
// To guarantee backwards compatibility scriptFormat is taken into account and has precedence before any other type
if (implementationType === 'script' || scriptFormat) {
return bpmnFactory.create('camunda:ExecutionListener', {
event,
script: bpmnFactory.create('camunda:Script', {
scriptFormat,
value
})
});
}
return bpmnFactory.create('camunda:ExecutionListener', {
event,
[implementationType]: value
});
}
/**
* Create camunda:field element containing string or expression from given binding.
*
* @param {PropertyBinding} binding
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaFieldInjection(binding, value, bpmnFactory) {
const DEFAULT_PROPS = {
'string': undefined,
'expression': undefined,
'name': undefined
};
const props = minDash.assign({}, DEFAULT_PROPS);
const {
expression,
name
} = binding;
if (!expression) {
props.string = value;
} else {
props.expression = value;
}
props.name = name;
return bpmnFactory.create('camunda:Field', props);
}
/**
* Create camunda:errorEventDefinition element containing expression and errorRef
* from given binding.
*
* @param {String} expression
* @param {ModdleElement} errorRef
* @param {ModdleElement} parent
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement}
*/
function createCamundaErrorEventDefinition(expression, errorRef, parent, bpmnFactory) {
const errorEventDefinition = bpmnFactory.create('camunda:ErrorEventDefinition', {
errorRef,
expression
});
errorEventDefinition.$parent = parent;
return errorEventDefinition;
}
/**
* Create bpmn:error element containing a specific error id given by a binding.
*
* @param {String} bindingErrorRef
* @param {ModdleElement} parent
* @param {BpmnFactory} bpmnFactory
*
* @return { ModdleElement }
*/
function createError(bindingErrorRef, parent, bpmnFactory) {
const error = bpmnFactory.create('bpmn:Error', {
// we need to later retrieve the error from a binding
id: nextId('Error_' + bindingErrorRef + '_')
});
error.$parent = parent;
return error;
}
// helpers //////////
/**
* Create properties for camunda:in and camunda:out types.
*/
function createCamundaInOutAttrs(binding, value) {
const properties = {};
const {
expression,
source,
sourceExpression,
target,
type,
variables
} = binding;
// explicitly cover all conditions as specified here:
// https://github.com/camunda/camunda-modeler/blob/develop/docs/element-templates/README.md#camundain
if (type === 'camunda:in') {
if (target && !expression && !variables) {
properties.target = target;
properties.source = value;
} else if (target && expression === true && !variables) {
properties.target = target;
properties.sourceExpression = value;
} else if (!target && !expression && variables === 'local') {
properties.local = true;
properties.variables = 'all';
} else if (target && !expression && variables === 'local') {
properties.local = true;
properties.source = value;
properties.target = target;
} else if (target && expression && variables === 'local') {
properties.local = true;
properties.sourceExpression = value;
properties.target = target;
} else if (!target && !expression && variables === 'all') {
properties.variables = 'all';
} else {
throw new Error('invalid configuration for camunda:in element template binding');
}
}
// explicitly cover all conditions as specified here:
// https://github.com/camunda/camunda-modeler/blob/develop/docs/element-templates/README.md#camundaout
if (type === 'camunda:out') {
if (source && !sourceExpression && !variables) {
properties.target = value;
properties.source = source;
} else if (!source && sourceExpression && !variables) {
properties.target = value;
properties.sourceExpression = sourceExpression;
} else if (!source && !sourceExpression && variables === 'all') {
properties.variables = 'all';
} else if (source && !sourceExpression && variables === 'local') {
properties.local = true;
properties.source = source;
properties.target = value;
} else if (!source && sourceExpression && variables === 'local') {
properties.local = true;
properties.sourceExpression = sourceExpression;
properties.target = value;
} else if (!source && !sourceExpression && variables === 'local') {
properties.local = true;
properties.variables = 'all';
} else {
throw new Error('invalid configuration for camunda:out element template binding');
}
}
return properties;
}
function getEventDefinition(element, eventType) {
const businessObject = ModelUtil.getBusinessObject(element);
const eventDefinitions = businessObject.get('eventDefinitions') || [];
return minDash.find(eventDefinitions, function(definition) {
return ModelUtil.is(definition, eventType);
});
}
function getSignalEventDefinition(element) {
return getEventDefinition(element, 'bpmn:SignalEventDefinition');
}
const CAMUNDA_SERVICE_TASK_LIKE = [
'camunda:class',
'camunda:delegateExpression',
'camunda:expression'
];
/**
* Applies an element template to an element. Sets `camunda:modelerTemplate` and
* `camunda:modelerTemplateVersion`.
*/
class ChangeElementTemplateHandler {
constructor(bpmnFactory, bpmnReplace, commandStack, modeling) {
this._bpmnFactory = bpmnFactory;
this._bpmnReplace = bpmnReplace;
this._commandStack = commandStack;
this._modeling = modeling;
}
/**
* Change an element's template and update its properties as specified in `newTemplate`. Specify
* `oldTemplate` to update from one template to another. If `newTemplate` isn't specified the
* `camunda:modelerTemplate` and `camunda:modelerTemplateVersion` properties will be removed from
* the element.
*
* @param {Object} context
* @param {Object} context.element
* @param {Object} [context.oldTemplate]
* @param {Object} [context.newTemplate]
*/
preExecute(context) {
const newTemplate = context.newTemplate,
oldTemplate = context.oldTemplate;
let element = context.element;
// update camunda:modelerTemplate attribute
this._updateCamundaModelerTemplate(element, newTemplate);
if (newTemplate) {
element = context.element = this._updateTaskType(element, newTemplate);
// update properties
this._updateProperties(element, oldTemplate, newTemplate);
// update camunda:ExecutionListener properties
this._updateCamundaExecutionListenerProperties(element, newTemplate);
// update camunda:Field properties
this._updateCamundaFieldProperties(element, oldTemplate, newTemplate);
// update camunda:In and camunda:Out properties
this._updateCamundaInOutProperties(element, oldTemplate, newTemplate);
// update camunda:InputParameter and camunda:OutputParameter properties
this._updateCamundaInputOutputParameterProperties(element, oldTemplate, newTemplate);
// update camunda:Property properties
this._updateCamundaPropertyProperties(element, oldTemplate, newTemplate);
// update camunda:ErrorEventDefinition properties
this._updateCamundaErrorEventDefinitionProperties(element, oldTemplate, newTemplate);
// update properties for each scope
handleLegacyScopes(newTemplate.scopes).forEach((newScopeTemplate) => {
this._updateScopeProperties(element, oldTemplate, newScopeTemplate, newTemplate);
});
}
}
_getOrCreateExtensionElements(element) {
const bpmnFactory = this._bpmnFactory,
modeling = this._modeling;
const businessObject = ModelUtil.getBusinessObject(element);
let extensionElements = businessObject.get('extensionElements');
if (!extensionElements) {
extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
values: []
});
extensionElements.$parent = businessObject;
modeling.updateProperties(element, {
extensionElements: extensionElements
});
}
return extensionElements;
}
/**
* Update `camunda:ErrorEventDefinition` properties of specified business object. Event
* definitions can only exist in `bpmn:ExtensionElements`.
*
* Ensures an bpmn:Error exists for the event definition.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCamundaErrorEventDefinitionProperties(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:errorEventDefinition';
});
// (1) do not override if no updates
if (!newProperties.length) {
return;
}
const extensionElements = this._getOrCreateExtensionElements(element);
const oldErrorEventDefinitions = findExtensions(element, [ 'camunda:ErrorEventDefinition' ]);
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
oldEventDefinition = oldProperty && findOldBusinessObject(extensionElements, oldProperty),
newBinding = newProperty.binding;
// (2) update old event definitions
if (oldProperty && oldEventDefinition) {
if (!propertyChanged(oldEventDefinition, oldProperty)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldEventDefinition,
properties: {
expression: newProperty.value
}
});
}
remove(oldErrorEventDefinitions, oldEventDefinition);
}
// (3) create new event definition + error
else {
const rootElement = getRoot(ModelUtil.getBusinessObject(element)),
newError = createError(newBinding.errorRef, rootElement, bpmnFactory),
newEventDefinition = createCamundaErrorEventDefinition(newProperty.value, newError, extensionElements, bpmnFactory);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: rootElement,
properties: {
rootElements: [ ...rootElement.get('rootElements'), newError ]
}
});
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), newEventDefinition ]
}
});
}
});
// (4) remove old event definitions
if (oldErrorEventDefinitions.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: minDash.without(extensionElements.get('values'), value => oldErrorEventDefinitions.includes(value))
}
});
}
}
/**
* Update `camunda:ExecutionListener` properties of specified business object. Execution listeners
* will always be overridden. Execution listeners can only exist in `bpmn:ExtensionElements`.
*
* @param {djs.model.Base} element
* @param {Object} newTemplate
*/
_updateCamundaExecutionListenerProperties(element, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:executionListener';
});
// (1) do not override old execution listeners if no new execution listeners specified
if (!newProperties.length) {
return;
}
const extensionElements = this._getOrCreateExtensionElements(element);
// (2) remove old execution listeners
const oldExecutionListeners = findExtensions(element, [ 'camunda:ExecutionListener' ]);
// (3) add new execution listeners
const newExecutionListeners = newProperties.map((newProperty) => {
const newBinding = newProperty.binding,
propertyValue = newProperty.value;
return createCamundaExecutionListener(newBinding, propertyValue, bpmnFactory);
});
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [
...minDash.without(extensionElements.get('values'), value => oldExecutionListeners.includes(value)),
...newExecutionListeners
]
}
});
}
/**
* Update `camunda:Field` properties of specified business object.
* If business object is `camunda:ExecutionListener` or `camunda:TaskListener` `fields` property
* will be updated. Otherwise `extensionElements.values` property will be updated.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
* @param {ModdleElement} businessObject
*/
_updateCamundaFieldProperties(element, oldTemplate, newTemplate, businessObject) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:field';
});
// (1) do not override old fields if no new fields specified
if (!newProperties.length) {
return;
}
if (!businessObject) {
businessObject = this._getOrCreateExtensionElements(element);
}
const propertyName = ModelUtil.isAny(businessObject, [ 'camunda:ExecutionListener', 'camunda:TaskListener' ])
? 'fields'
: 'values';
const oldFields = findExtensions(element, [ 'camunda:Field' ]);
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
oldField = oldProperty && findOldBusinessObject(businessObject, oldProperty),
newBinding = newProperty.binding;
// (2) update old fields
if (oldProperty && oldField) {
if (!propertyChanged(oldField, oldProperty)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldField,
properties: {
string: newProperty.value
}
});
}
remove(oldFields, oldField);
}
// (3) add new fields
else {
const newCamundaFieldInjection = createCamundaFieldInjection(newBinding, newProperty.value, bpmnFactory);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
[ propertyName ]: [ ...businessObject.get(propertyName), newCamundaFieldInjection ]
}
});
}
});
// (4) remove old fields
if (oldFields.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
[ propertyName ]: minDash.without(businessObject.get(propertyName), value => oldFields.includes(value))
}
});
}
}
/**
* Update `camunda:In` and `camunda:Out` properties of specified business object. Only
* `bpmn:CallActivity` and events with `bpmn:SignalEventDefinition` can have ins. Only
* `camunda:CallActivity` can have outs.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCamundaInOutProperties(element, oldTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:in'
|| newBindingType === 'camunda:in:businessKey'
|| newBindingType === 'camunda:out';
});
// (1) do not override old fields if no new fields specified
if (!newProperties.length) {
return;
}
// get extension elements of either signal event definition or call activity
const extensionElements = this._getOrCreateExtensionElements(getSignalEventDefinition(element) || element);
const oldInsAndOuts = findExtensions(extensionElements, [ 'camunda:In', 'camunda:Out' ]);
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
oldBinding = oldProperty && oldProperty.binding,
oldInOurOut = oldProperty && findOldBusinessObject(extensionElements, oldProperty),
newPropertyValue = newProperty.value,
newBinding = newProperty.binding,
newBindingType = newBinding.type,
properties = {};
let newInOrOut;
// (2) update old ins and outs
if (oldProperty && oldInOurOut) {
if (!propertyChanged(oldInOurOut, oldProperty)) {
if (newBindingType === 'camunda:in') {
if (newBinding.expression) {
properties[ 'camunda:sourceExpression' ] = newPropertyValue;
} else {
properties[ 'camunda:source' ] = newPropertyValue;
}
} else if (newBindingType === 'camunda:in:businessKey') {
properties[ 'camunda:businessKey' ] = newPropertyValue;
} else if (newBindingType === 'camunda:out') {
properties[ 'camunda:target' ] = newPropertyValue;
}
}
// update camunda:local property if it changed
if ((oldBinding.local && !newBinding.local) || !oldBinding.local && newBinding.local) {
properties.local = newBinding.local;
}
if (minDash.keys(properties)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldInOurOut,
properties
});
}
remove(oldInsAndOuts, oldInOurOut);
}
// (3) add new ins and outs
else {
if (newBindingType === 'camunda:in') {
newInOrOut = createCamundaIn(newBinding, newPropertyValue, bpmnFactory);
} else if (newBindingType === 'camunda:out') {
newInOrOut = createCamundaOut(newBinding, newPropertyValue, bpmnFactory);
} else if (newBindingType === 'camunda:in:businessKey') {
newInOrOut = createCamundaInWithBusinessKey(newPropertyValue, bpmnFactory);
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), newInOrOut ]
}
});
}
});
// (4) remove old ins and outs
if (oldInsAndOuts.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: minDash.without(extensionElements.get('values'), value => oldInsAndOuts.includes(value))
}
});
}
}
/**
* Update `camunda:InputParameter` and `camunda:OutputParameter` properties of specified business
* object. Both can only exist in `camunda:InputOutput` which can exist in `bpmn:ExtensionElements`
* or `camunda:Connector`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
*/
_updateCamundaInputOutputParameterProperties(element, oldTemplate, newTemplate, businessObject) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:inputParameter' || newBindingType === 'camunda:outputParameter';
});
// (1) do not override old inputs and outputs if no new inputs and outputs specified
if (!newProperties.length) {
return;
}
if (!businessObject) {
businessObject = this._getOrCreateExtensionElements(element);
}
let inputOutput;
if (ModelUtil.is(businessObject, 'camunda:Connector')) {
inputOutput = businessObject.get('camunda:inputOutput');
if (!inputOutput) {
inputOutput = bpmnFactory.create('camunda:InputOutput');
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
inputOutput
}
});
}
} else {
inputOutput = findExtension(businessObject, 'camunda:InputOutput');
if (!inputOutput) {
inputOutput = bpmnFactory.create('camunda:InputOutput');
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), inputOutput ]
}
});
}
}
const oldInputs = inputOutput.get('camunda:inputParameters')
? inputOutput.get('camunda:inputParameters').slice()
: [];
const oldOutputs = inputOutput.get('camunda:outputParameters')
? inputOutput.get('camunda:outputParameters').slice()
: [];
let propertyName;
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
oldInputOrOutput = oldProperty && findOldBusinessObject(businessObject, oldProperty),
newPropertyValue = newProperty.value,
newBinding = newProperty.binding,
newBindingType = newBinding.type;
let newInputOrOutput,
properties;
// (2) update old inputs and outputs
if (oldProperty && oldInputOrOutput) {
if (!propertyChanged(oldInputOrOutput, oldProperty)) {
if (ModelUtil.is(oldInputOrOutput, 'camunda:InputParameter')) {
properties = {
value: newPropertyValue
};
} else {
properties = {
name: newPropertyValue
};
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldInputOrOutput,
properties
});
}
if (ModelUtil.is(oldInputOrOutput, 'camunda:InputParameter')) {
remove(oldInputs, oldInputOrOutput);
} else {
remove(oldOutputs, oldInputOrOutput);
}
}
// (3) add new inputs and outputs
else {
if (newBindingType === 'camunda:inputParameter') {
propertyName = 'inputParameters';
newInputOrOutput = createInputParameter(newBinding, newPropertyValue, bpmnFactory);
} else {
propertyName = 'outputParameters';
newInputOrOutput = createOutputParameter(newBinding, newPropertyValue, bpmnFactory);
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: inputOutput,
properties: {
[ propertyName ]: [ ...inputOutput.get(propertyName), newInputOrOutput ]
}
});
}
});
// (4) remove old inputs and outputs
if (oldInputs.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: inputOutput,
properties: {
inputParameters: minDash.without(inputOutput.get('inputParameters'), inputParameter => oldInputs.includes(inputParameter))
}
});
}
if (oldOutputs.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: inputOutput,
properties: {
outputParameters: minDash.without(inputOutput.get('outputParameters'), outputParameter => oldOutputs.includes(outputParameter))
}
});
}
}
_updateCamundaModelerTemplate(element, newTemplate) {
const modeling = this._modeling;
const newId = newTemplate && newTemplate.id;
const newVersion = newTemplate && newTemplate.version;
if (getTemplateId(element) !== newId || getTemplateVersion(element) !== newVersion) {
modeling.updateProperties(element, {
'camunda:modelerTemplate': newId,
'camunda:modelerTemplateVersion': newVersion
});
}
}
/**
* Update `camunda:Property` properties of specified business object. `camunda:Property` can only
* exist in `camunda:Properties`.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newTemplate
* @param {ModdleElement} businessObject
*/
_updateCamundaPropertyProperties(element, oldTemplate, newTemplate, businessObject) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'camunda:property';
});
// (1) do not override old properties if no new properties specified
if (!newProperties.length) {
return;
}
if (businessObject) {
businessObject = this._getOrCreateExtensionElements(businessObject);
} else {
businessObject = this._getOrCreateExtensionElements(element);
}
let camundaProperties = findExtension(businessObject, 'camunda:Properties');
if (!camundaProperties) {
camundaProperties = bpmnFactory.create('camunda:Properties');
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
values: [ ...businessObject.get('values'), camundaProperties ]
}
});
}
const oldCamundaProperties = camundaProperties.get('camunda:values')
? camundaProperties.get('camunda:values').slice()
: [];
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
oldCamundaProperty = oldProperty && findOldBusinessObject(businessObject, oldProperty),
newPropertyValue = newProperty.value,
newBinding = newProperty.binding;
// (2) update old properties
if (oldProperty && oldCamundaProperty) {
if (!propertyChanged(oldCamundaProperty, oldProperty)) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: oldCamundaProperty,
properties: {
value: newPropertyValue
}
});
}
remove(oldCamundaProperties, oldCamundaProperty);
}
// (3) add new properties
else {
const newCamundaProperty = createCamundaProperty(newBinding, newPropertyValue, bpmnFactory);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: camundaProperties,
properties: {
values: [ ...camundaProperties.get('values'), newCamundaProperty ]
}
});
}
});
// (4) remove old properties
if (oldCamundaProperties.length) {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: camundaProperties,
properties: {
values: minDash.without(camundaProperties.get('values'), value => oldCamundaProperties.includes(value))
}
});
}
}
/**
* Update `bpmn:conditionExpression` property of specified element. Since condition expression is
* is not primitive it needs special handling.
*
* @param {djs.model.Base} element
* @param {Object} oldProperty
* @param {Object} newProperty
*/
_updateConditionExpression(element, oldProperty, newProperty) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack,
modeling = this._modeling;
const newBinding = newProperty.binding,
newPropertyValue = newProperty.value;
if (!oldProperty) {
modeling.updateProperties(element, {
conditionExpression: bpmnFactory.create('bpmn:FormalExpression', {
body: newPropertyValue,
language: newBinding.scriptFormat
})
});
return;
}
const oldBinding = oldProperty.binding,
oldPropertyValue = oldProperty.value;
const businessObject = ModelUtil.getBusinessObject(element),
conditionExpression = businessObject.get('bpmn:conditionExpression');
const properties = {};
if (conditionExpression.get('body') === oldPropertyValue) {
properties.body = newPropertyValue;
}
if (conditionExpression.get('language') === oldBinding.scriptFormat) {
properties.language = newBinding.scriptFormat;
}
if (!minDash.keys(properties).length) {
return;
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: conditionExpression,
properties
});
}
_updateProperties(element, oldTemplate, newTemplate, businessObject) {
const commandStack = this._commandStack;
const newProperties = newTemplate.properties.filter((newProperty) => {
const newBinding = newProperty.binding,
newBindingType = newBinding.type;
return newBindingType === 'property';
});
const oldProperties = oldTemplate && oldTemplate.properties.filter((oldProperty) => {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === 'property';
});
if (!newProperties.length) {
return;
}
if (!businessObject) {
businessObject = ModelUtil.getBusinessObject(element);
}
newProperties.forEach((newProperty) => {
const oldProperty = findOldProperty(oldTemplate, newProperty),
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newPropertyValue = newProperty.value;
let changedElement,
properties;
if (oldProperty) {
remove(oldProperties, oldProperty);
}
if (newBindingName === 'conditionExpression') {
this._updateConditionExpression(element, oldProperty, newProperty);
} else {
if (ModelUtil.is(businessObject, 'bpmn:Error')) {
changedElement = businessObject;
} else {
changedElement = element;
}
if (oldProperty && propertyChanged(changedElement, oldProperty)) {
return;
}
properties = {};
properties[ newBindingName ] = newPropertyValue;
// only one of `camunda:class`, `camunda:delegateExpression` and `camunda:expression` can be set
// TODO(philippfromme): ensuring only one of these properties is set at a time should be
// implemented in a behavior and not in this handler and properties panel UI
if (CAMUNDA_SERVICE_TASK_LIKE.indexOf(newBindingName) !== -1) {
CAMUNDA_SERVICE_TASK_LIKE.forEach((camundaServiceTaskLikeProperty) => {
if (camundaServiceTaskLikeProperty !== newBindingName) {
properties[ camundaServiceTaskLikeProperty ] = undefined;
}
});
}
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties
});
}
});
// remove old properties not present in new template
oldProperties && oldProperties.forEach((oldProperty) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject,
properties: {
[oldProperty.binding.name]: null
}
});
});
}
/**
* Update properties for a specified scope.
*
* @param {djs.model.Base} element
* @param {Object} oldTemplate
* @param {Object} newScopeTemplate
* @param {Object} newTemplate
*/
_updateScopeProperties(element, oldTemplate, newScopeTemplate, newTemplate) {
const bpmnFactory = this._bpmnFactory,
commandStack = this._commandStack;
const scopeName = newScopeTemplate.type;
let scopeElement;
scopeElement = findOldScopeElement(element, newScopeTemplate, newTemplate);
if (!scopeElement) {
scopeElement = bpmnFactory.create(scopeName);
}
const oldScopeTemplate = findOldScopeTemplate(newScopeTemplate, oldTemplate);
// update properties
this._updateProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);
// update camunda:ExecutionListener properties
this._updateCamundaExecutionListenerProperties(element, newScopeTemplate);
// update camunda:In and camunda:Out properties
this._updateCamundaInOutProperties(element, oldScopeTemplate, newScopeTemplate);
// update camunda:InputParameter and camunda:OutputParameter properties
this._updateCamundaInputOutputParameterProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);
// update camunda:Field properties
this._updateCamundaFieldProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);
// update camunda:Property properties
this._updateCamundaPropertyProperties(element, oldScopeTemplate, newScopeTemplate, scopeElement);
// assume that root elements were already created in root by referenced event definition binding
if (isRootElementScope(scopeName)) {
return;
}
const extensionElements = this._getOrCreateExtensionElements(element);
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: extensionElements,
properties: {
values: [ ...extensionElements.get('values'), scopeElement ]
}
});
}
/**
* Replaces the element with the specified elementType
*
* @param {djs.model.Base} element
* @param {Object} newTemplate
*/
_updateTaskType(element, newTemplate) {
// determine new task type
const newType = newTemplate.elementType;
if (!newType) {
return element;
}
// don't replace Task that is already the correct type
if (element.$type === newType.value) {
return element;
}
return this._bpmnReplace.replaceElement(element, { type: newType.value });
}
}
ChangeElementTemplateHandler.$inject = [
'bpmnFactory',
'bpmnReplace',
'commandStack',
'modeling'
];
// helpers //////////
/**
* Find old business object matching specified old property.
*
* @param {djs.model.Base|ModdleElement} element
* @param {Object} oldProperty
*
* @returns {ModdleElement}
*/
function findOldBusinessObject(element, oldProperty) {
let businessObject = ModelUtil.getBusinessObject(element),
propertyName;
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType === 'camunda:field') {
if (ModelUtil.isAny(businessObject, [ 'camunda:ExecutionListener', 'camunda:TaskListener' ])) {
propertyName = 'camunda:fields';
} else {
propertyName = 'bpmn:values';
}
if (!businessObject || !businessObject.get(propertyName) || !businessObject.get(propertyName).length) {
return;
}
return minDash.find(businessObject.get(propertyName), function(oldBusinessObject) {
return oldBusinessObject.get('camunda:name') === oldBinding.name;
});
}
if (oldBindingType === 'camunda:in') {
return minDash.find(businessObject.get('values'), function(oldBusinessObject) {
return oldBusinessObject.get('target') === oldBinding.target;
});
}
if (oldBindingType === 'camunda:in:businessKey') {
return minDash.find(businessObject.get('values'), function(oldBusinessObject) {
return minDash.isString(oldBusinessObject.get('businessKey'));
});
}
if (oldBindingType === 'camunda:out') {
return minDash.find(businessObject.get('values'), function(oldBusinessObject) {
return oldBusinessObject.get('source') === oldBinding.source ||
oldBusinessObject.get('sourceExpression') || oldBinding.sourceExpression;
});
}
if (oldBindingType === 'camunda:inputParameter' || oldBindingType === 'camunda:outputParameter') {
if (ModelUtil.is(businessObject, 'camunda:Connector')) {
businessObject = businessObject.get('camunda:inputOutput');
if (!businessObject) {
return;
}
} else {
businessObject = findExtension(businessObject, 'camunda:InputOutput');
if (!businessObject) {
return;
}
}
if (oldBindingType === 'camunda:inputParameter') {
return minDash.find(businessObject.get('camunda:inputParameters'), function(oldBusinessObject) {
return oldBusinessObject.get('camunda:name') === oldBinding.name;
});
} else {
return minDash.find(businessObject.get('camunda:outputParameters'), function(oldBusinessObject) {
if (oldBinding.scriptFormat) {
const definition = oldBusinessObject.get('camunda:definition');
return definition && definition.get('camunda:value') === oldBinding.source;
} else {
return oldBusinessObject.get('camunda:value') === oldBinding.source;
}
});
}
}
if (oldBindingType === 'camunda:property') {
if (!businessObject || !businessObject.get('values') || !businessObject.get('values').length) {
return;
}
businessObject = findExtension(businessObject, 'camunda:Properties');
if (!businessObject) {
return;
}
return minDash.find(businessObject.get('values'), function(oldBusinessObject) {
return oldBusinessObject.get('camunda:name') === oldBinding.name;
});
}
if (oldBindingType === 'camunda:errorEventDefinition') {
return findCamundaErrorEventDefinition(element, oldBinding.errorRef);
}
}
/**
* Find old property matching specified new property.
*
* @param {Object} oldTemplate
* @param {Object} newProperty
*
* @returns {Object}
*/
function findOldProperty(oldTemplate, newProperty) {
if (!oldTemplate) {
return;
}
const oldProperties = oldTemplate.properties,
newBinding = newProperty.binding,
newBindingName = newBinding.name,
newBindingType = newBinding.type;
if (newBindingType === 'property') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
return oldBindingType === 'property' && oldBindingName === newBindingName;
});
}
if (newBindingType === 'camunda:field') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
return oldBindingType === 'camunda:field' && oldBindingName === newBindingName;
});
}
if (newBindingType === 'camunda:in') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'camunda:in') {
return;
}
// always override if change from source to source expression or vice versa
if ((oldBinding.expression && !newBinding.expression) ||
!oldBinding.expression && newBinding.expression) {
return;
}
return oldBinding.target === newBinding.target;
});
}
if (newBindingType === 'camunda:in:businessKey') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === 'camunda:in:businessKey';
});
}
if (newBindingType === 'camunda:out') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
return oldBindingType === 'camunda:out' && (
oldBinding.source === newBinding.source ||
oldBinding.sourceExpression === newBinding.sourceExpression
);
});
}
if (newBindingType === 'camunda:inputParameter') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'camunda:inputParameter') {
return;
}
return oldBindingName === newBindingName
&& oldBinding.scriptFormat === newBinding.scriptFormat;
});
}
if (newBindingType === 'camunda:outputParameter') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingType = oldBinding.type;
if (oldBindingType !== 'camunda:outputParameter') {
return;
}
return oldBinding.source === newBinding.source
&& oldBinding.scriptFormat === newBinding.scriptFormat;
});
}
if (newBindingType === 'camunda:property') {
return minDash.find(oldProperties, function(oldProperty) {
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type;
return oldBindingType === 'camunda:property' && oldBindingName === newBindingName;
});
}
if (newBindingType === 'camunda:errorEventDefinition') {
return minDash.find(oldProperties, function(oldProperty) {
const newBindingRef = newBinding.errorRef,
oldBinding = oldProperty.binding,
oldBindingRef = oldBinding.errorRef,
oldBindingType = oldBinding.type;
return oldBindingType === 'camunda:errorEventDefinition'
&& oldBindingRef === newBindingRef;
});
}
}
function findOldScopeElement(element, scopeTemplate, template) {
const scopeName = scopeTemplate.type,
id = scopeTemplate.id;
if (scopeName === 'camunda:Connector') {
return findExtension(element, 'camunda:Connector');
}
if (scopeName === 'bpmn:Error') {
// (1) find by error event definition binding
const errorEventDefinitionBinding = findErrorEventDefinitionBinding(template, id);
if (!errorEventDefinitionBinding) {
return;
}
// (2) find error event definition
const errorEventDefinition = findOldBusinessObject(element, errorEventDefinitionBinding);
if (!errorEventDefinition) {
return;
}
// (3) retrieve referenced error
return errorEventDefinition.errorRef;
}
}
function isRootElementScope(scopeName) {
return [ 'bpmn:Error' ].includes(scopeName);
}
function findOldScopeTemplate(scopeTemplate, oldTemplate) {
const scopeName = scopeTemplate.type,
scopeId = scopeTemplate.id,
scopes = oldTemplate && handleLegacyScopes(oldTemplate.scopes);
return scopes && minDash.find(scopes, function(scope) {
if (isRootElementScope(scopeName)) {
return scope.id === scopeId;
}
return scope.type === scopeName;
});
}
function findErrorEventDefinitionBinding(template, templateErrorId) {
return minDash.find(template.properties, function(property) {
return property.binding.errorRef === templateErrorId;
});
}
/**
* Check whether property was changed after being set by template.
*
* @param {djs.model.Base|ModdleElement} element
* @param {Object} oldProperty
*
* @returns {boolean}
*/
function propertyChanged(element, oldProperty) {
const businessObject = ModelUtil.getBusinessObject(element);
const oldBinding = oldProperty.binding,
oldBindingName = oldBinding.name,
oldBindingType = oldBinding.type,
oldPropertyValue = oldProperty.value;
let conditionExpression,
definition;
if (oldBindingType === 'property') {
if (oldBindingName === 'conditionExpression') {
conditionExpression = businessObject.get('bpmn:conditionExpression');
return conditionExpression.get('bpmn:body') !== oldPropertyValue;
}
return businessObject.get(oldBindingName) !== oldPropertyValue;
}
if (oldBindingType === 'camunda:field') {
return businessObject.get('camunda:string') !== oldPropertyValue;
}
if (oldBindingType === 'camunda:in') {
if (oldBinding.expression) {
return businessObject.get('sourceExpression') !== oldPropertyValue;
} else {
return businessObject.get('camunda:source') !== oldPropertyValue;
}
}
if (oldBindingType === 'camunda:in:businessKey') {
return businessObject.get('camunda:businessKey') !== oldPropertyValue;
}
if (oldBindingType === 'camunda:out') {
return businessObject.get('camunda:target') !== oldPropertyValue;
}
if (oldBindingType === 'camunda:inputParameter') {
if (oldBinding.scriptFormat) {
definition = businessObject.get('camunda:definition');
return definition && definition.get('camunda:value') !== oldPropertyValue;
} else {
return businessObject.get('camunda:value') !== oldPropertyValue;
}
}
if (oldBindingType === 'camunda:outputParameter') {
return businessObject.get('camunda:name') !== oldPropertyValue;
}
if (oldBindingType === 'camunda:property') {
return businessObject.get('camunda:value') !== oldPropertyValue;
}
if (oldBindingType === 'camunda:errorEventDefinition') {
return businessObject.get('expression') !== oldPropertyValue;
}
}
function remove(array, item) {
const index = array.indexOf(item);
if (minDash.isUndefined(index)) {
return array;
}
array.splice(index, 1);
return array;
}
class RemoveElementTemplateHandler {
constructor(
modeling,
elementFactory,
elementRegistry,
canvas,
bpmnFactory,
replace,
commandStack
) {
this._modeling = modeling;
this._elementFactory = elementFactory;
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._bpmnFactory = bpmnFactory;
this._replace = replace;
this._commandStack = commandStack;
}
preExecute(context) {
const {
element
} = context;
if (element.parent) {
context.newElement = this._removeTemplate(element);
} else {
context.newElement = this._removeRootTemplate(element);
}
}
_removeTemplate(element) {
const replace = this._replace;
const businessObject = ModelUtil.getBusinessObject(element);
const type = businessObject.$type,
eventDefinitionType = this._getEventDefinitionType(businessObject);
const newBusinessObject = this._createBlankBusinessObject(element);
return replace.replaceElement(element,
{
type: type,
businessObject: newBusinessObject,
eventDefinitionType: eventDefinitionType,
},
{
createElementsBehavior: false
}
);
}
/**
* Remove template from a given element.
*
* @param {djs.model.Base} element
*
* @return {djs.model.Base} the updated element
*/
_removeRootTemplate(element) {
var modeling = this._modeling,
elementFactory = this._elementFactory,
elementRegistry = this._elementRegistry,
canvas = this._canvas;
// We are inside a collapsed subprocess, move up to the parent before replacing the collapsed object
if (DrilldownUtil.isPlane(element)) {
const shapeId = DrilldownUtil.getShapeIdFromPlane(element);
const shape = elementRegistry.get(shapeId);
if (shape && shape !== element) {
canvas.setRootElement(canvas.findRoot(shape));
return this._removeTemplate(shape);
}
}
const businessObject = ModelUtil.getBusinessObject(element);
const type = businessObject.$type;
const newBusinessObject = this._createBlankBusinessObject(element);
const newRoot = elementFactory.create('root', {
type: type,
businessObject: newBusinessObject
});
this._commandStack.execute('canvas.updateRoot', {
newRoot: newRoot,
oldRoot: element
});
modeling.moveElements(element.children, { x: 0, y: 0 }, newRoot);
return newRoot;
}
_getEventDefinitionType(businessObject) {
if (!businessObject.eventDefinitions) {
return null;
}
const eventDefinition = businessObject.eventDefinitions[ 0 ];
if (!eventDefinition) {
return null;
}
return eventDefinition.$type;
}
_createBlankBusinessObject(element) {
const bpmnFactory = this._bpmnFactory;
const bo = ModelUtil.getBusinessObject(element),
newBo = bpmnFactory.create(bo.$type),
label = LabelUtil.getLabel(element);
if (!label) {
return newBo;
}
if (ModelUtil.is(element, 'bpmn:Group')) {
newBo.categoryValueRef = bpmnFactory.create('bpmn:CategoryValue');
}
LabelUtil.setLabel({ businessObject: newBo }, label);
return newBo;
}
}
RemoveElementTemplateHandler.$inject = [
'modeling',
'elementFactory',
'elementRegistry',
'canvas',
'bpmnFactory',
'replace',
'commandStack'
];
class ElementTemplatesCommands {
constructor(commandStack, elementTemplates, eventBus) {
commandStack.registerHandler(
'element-templates.multi-command-executor',
MultiCommandHandler
);
commandStack.registerHandler(
'propertiesPanel.camunda.changeTemplate',
ChangeElementTemplateHandler
);
commandStack.registerHandler(
'propertiesPanel.removeTemplate',
RemoveElementTemplateHandler
);
// apply default element templates on shape creation
eventBus.on([ 'commandStack.shape.create.postExecuted' ], function(event) {
const {
context: {
hints = {},
shape
}
} = event;
if (hints.createElementsBehavior !== false) {
applyDefaultTemplate(shape, elementTemplates, commandStack);
}
});
// apply default element templates on connection creation
eventBus.on([ 'commandStack.connection.create.postExecuted' ], function(event) {
const {
context: {
hints = {},
connection
}
} = event;
if (hints.createElementsBehavior !== false) {
applyDefaultTemplate(connection, elementTemplates, commandStack);
}
});
}
}
ElementTemplatesCommands.$inject = [ 'commandStack', 'elementTemplates', 'eventBus' ];
function applyDefaultTemplate(element, elementTemplates, commandStack) {
if (!elementTemplates.get(element) && elementTemplates.getDefault(element)) {
const command = 'propertiesPanel.camunda.changeTemplate';
const commandContext = {
element: element,
newTemplate: elementTemplates.getDefault(element)
};
commandStack.execute(command, commandContext);
}
}
var commandsModule = {
__init__: [ 'elementTemplateCommands' ],
elementTemplateCommands: [ 'type', ElementTemplatesCommands ]
};
/**
* This Behavior checks if the new element's type is in
* the list of elements the template applies to and unlinks
* it if not.
*/
class ReplaceBehavior extends CommandInterceptor {
constructor(elementTemplates, injector) {
super(injector.get('eventBus'));
this.postExecuted('shape.replace', function(e) {
var context = e.context,
oldShape = context.oldShape,
oldBo = ModelUtil.getBusinessObject(oldShape),
newShape = context.newShape,
newBo = ModelUtil.getBusinessObject(newShape);
if (!oldBo.modelerTemplate) {
return;
}
const template = newBo.modelerTemplate;
const version = newBo.modelerTemplateVersion;
const elementTemplate = elementTemplates.get(template, version);
if (!elementTemplate) {
elementTemplates.unlinkTemplate(newShape, injector);
return;
}
const { appliesTo, elementType } = elementTemplate;
if (elementType) {
if (!ModelUtil.is(newShape, elementType.value)) {
elementTemplates.unlinkTemplate(newShape, injector);
}
return;
}
const allowed = appliesTo.reduce((allowed, type) => {
return allowed || ModelUtil.is(newBo, type);
}, false);
if (!allowed) {
elementTemplates.unlinkTemplate(newShape, injector);
}
});
}
}
ReplaceBehavior.$inject = [
'elementTemplates',
'injector'
];
var behaviorModule = {
__init__: [
'elementTemplatesReplaceBehavior'
],
elementTemplatesReplaceBehavior: [ 'type', ReplaceBehavior ]
};
var index = {
__depends__: [
commandsModule,
behaviorModule
],
__init__: [
'elementTemplatesLoader'
],
elementTemplates: [ 'type', ElementTemplates$1 ],
elementTemplatesLoader: [ 'type', ElementTemplatesLoader$1 ]
};
exports.CloudElementTemplatesCoreModule = index$1;
exports.ElementTemplatesCoreModule = index;
//# sourceMappingURL=core.js.map