bpmn-js-properties-panel
Version:
A simple properties panel for bpmn-js
605 lines (466 loc) • 15.2 kB
JavaScript
'use strict';
var entryFactory = require('../../../../factory/EntryFactory'),
getBusinessObject = require('bpmn-js/lib/util/ModelUtil').getBusinessObject,
getTemplate = require('../Helper').getTemplate,
cmdHelper = require('../../../../helper/CmdHelper');
var findExtension = require('../Helper').findExtension,
findInputParameter = require('../Helper').findInputParameter,
findOutputParameter = require('../Helper').findOutputParameter,
findCamundaProperty = require('../Helper').findCamundaProperty,
findCamundaInOut = require('../Helper').findCamundaInOut;
var createCamundaProperty = require('../CreateHelper').createCamundaProperty,
createInputParameter = require('../CreateHelper').createInputParameter,
createOutputParameter = require('../CreateHelper').createOutputParameter,
createCamundaIn = require('../CreateHelper').createCamundaIn,
createCamundaOut = require('../CreateHelper').createCamundaOut,
createCamundaInWithBusinessKey = require('../CreateHelper').createCamundaInWithBusinessKey;
var CAMUNDA_PROPERTY_TYPE = 'camunda:property',
CAMUNDA_INPUT_PARAMETER_TYPE = 'camunda:inputParameter',
CAMUNDA_OUTPUT_PARAMETER_TYPE = 'camunda:outputParameter',
CAMUNDA_IN_TYPE = 'camunda:in',
CAMUNDA_OUT_TYPE = 'camunda:out',
CAMUNDA_IN_BUSINESS_KEY_TYPE = 'camunda:in:businessKey',
CAMUNDA_EXECUTION_LISTENER_TYPE = 'camunda:executionListener';
var BASIC_MODDLE_TYPES = [
'Boolean',
'Integer',
'String'
];
var EXTENSION_BINDING_TYPES = [
CAMUNDA_PROPERTY_TYPE,
CAMUNDA_INPUT_PARAMETER_TYPE,
CAMUNDA_OUTPUT_PARAMETER_TYPE,
CAMUNDA_IN_TYPE,
CAMUNDA_OUT_TYPE,
CAMUNDA_IN_BUSINESS_KEY_TYPE
];
var IO_BINDING_TYPES = [
CAMUNDA_INPUT_PARAMETER_TYPE,
CAMUNDA_OUTPUT_PARAMETER_TYPE
];
var IN_OUT_BINDING_TYPES = [
CAMUNDA_IN_TYPE,
CAMUNDA_OUT_TYPE,
CAMUNDA_IN_BUSINESS_KEY_TYPE
];
/**
* Injects custom properties into the given group.
*
* @param {GroupDescriptor} group
* @param {djs.model.Base} element
* @param {ElementTemplates} elementTemplates
* @param {BpmnFactory} bpmnFactory
*/
module.exports = function(group, element, elementTemplates, bpmnFactory, translate) {
var template = getTemplate(element, elementTemplates);
if (!template) {
return;
}
template.properties.forEach(function(p, idx) {
var id = 'custom-' + template.id + '-' + idx;
var propertyType = p.type;
var entryOptions = {
id: id,
description: p.description,
label: p.label,
modelProperty: id,
get: propertyGetter(id, p),
set: propertySetter(id, p, bpmnFactory),
validate: propertyValidator(id, p)
};
var entry;
if (propertyType === 'Boolean') {
entry = entryFactory.checkbox(entryOptions);
}
if (propertyType === 'String') {
entry = entryFactory.textField(entryOptions);
}
if (propertyType === 'Text') {
entry = entryFactory.textBox(entryOptions);
}
if (propertyType === 'Dropdown') {
entryOptions.selectOptions = p.choices;
entry = entryFactory.selectBox(entryOptions);
}
if (entry) {
group.entries.push(entry);
}
});
};
/////// getters, setters and validators ///////////////
/**
* Return a getter that retrieves the given property.
*
* @param {String} name
* @param {PropertyDescriptor} property
*
* @return {Function}
*/
function propertyGetter(name, property) {
/* getter */
return function get(element) {
var value = getPropertyValue(element, property);
return objectWithKey(name, value);
};
}
/**
* Return a setter that updates the given property.
*
* @param {String} name
* @param {PropertyDescriptor} property
* @param {BpmnFactory} bpmnFactory
*
* @return {Function}
*/
function propertySetter(name, property, bpmnFactory) {
/* setter */
return function set(element, values) {
var value = values[name];
return setPropertyValue(element, property, value, bpmnFactory);
};
}
/**
* Return a validator that ensures the property is ok.
*
* @param {String} name
* @param {PropertyDescriptor} property
*
* @return {Function}
*/
function propertyValidator(name, property) {
/* validator */
return function validate(element, values) {
var value = values[name];
var error = validateValue(value, property);
if (error) {
return objectWithKey(name, error);
}
};
}
//////// get, set and validate helpers ///////////////////
/**
* Return the value of the specified property descriptor,
* on the passed diagram element.
*
* @param {djs.model.Base} element
* @param {PropertyDescriptor} property
*
* @return {Any}
*/
function getPropertyValue(element, property) {
var bo = getBusinessObject(element);
var binding = property.binding;
var bindingType = binding.type,
bindingName = binding.name;
var propertyValue = property.value || '';
// property
if (bindingType === 'property') {
var value = bo.get(bindingName);
if (bindingName === 'conditionExpression') {
if (value) {
return value.body;
} else {
// return defined default
return propertyValue;
}
} else {
// return value; default to defined default
return typeof value !== 'undefined' ? value : propertyValue;
}
}
var camundaProperties,
camundaProperty;
if (bindingType === CAMUNDA_PROPERTY_TYPE) {
camundaProperties = findExtension(bo, 'camunda:Properties');
if (camundaProperties) {
camundaProperty = findCamundaProperty(camundaProperties, binding);
if (camundaProperty) {
return camundaProperty.value;
}
}
return propertyValue;
}
var inputOutput,
ioParameter;
if (IO_BINDING_TYPES.indexOf(bindingType) !== -1) {
inputOutput = findExtension(bo, 'camunda:InputOutput');
if (!inputOutput) {
// ioParameter cannot exist yet, return property value
return propertyValue;
}
}
// camunda input parameter
if (bindingType === CAMUNDA_INPUT_PARAMETER_TYPE) {
ioParameter = findInputParameter(inputOutput, binding);
if (ioParameter) {
if (binding.scriptFormat) {
if (ioParameter.definition) {
return ioParameter.definition.value;
}
} else {
return ioParameter.value;
}
}
return propertyValue;
}
// camunda output parameter
if (binding.type === CAMUNDA_OUTPUT_PARAMETER_TYPE) {
ioParameter = findOutputParameter(inputOutput, binding);
if (ioParameter) {
return ioParameter.name;
}
return propertyValue;
}
var ioElement;
if (IN_OUT_BINDING_TYPES.indexOf(bindingType) != -1) {
ioElement = findCamundaInOut(bo, binding);
if (ioElement) {
if (bindingType === CAMUNDA_IN_BUSINESS_KEY_TYPE) {
return ioElement.businessKey;
} else
if (bindingType === CAMUNDA_OUT_TYPE) {
return ioElement.target;
} else
if (bindingType === CAMUNDA_IN_TYPE) {
return ioElement[binding.expression ? 'sourceExpression' : 'source'];
}
}
return propertyValue;
}
if (bindingType === CAMUNDA_EXECUTION_LISTENER_TYPE) {
var executionListener = findExtension(bo, 'camunda:ExecutionListener');
return executionListener.script.value;
}
throw unknownPropertyBinding(property);
}
module.exports.getPropertyValue = getPropertyValue;
/**
* Return an update operation that changes the diagram
* element's custom property to the given value.
*
* The response of this method will be processed via
* {@link PropertiesPanel#applyChanges}.
*
* @param {djs.model.Base} element
* @param {PropertyDescriptor} property
* @param {String} value
* @param {BpmnFactory} bpmnFactory
*
* @return {Object|Array<Object>} results to be processed
*/
function setPropertyValue(element, property, value, bpmnFactory) {
var bo = getBusinessObject(element);
var binding = property.binding;
var bindingType = binding.type,
bindingName = binding.name;
var propertyValue;
var updates = [];
// property
if (bindingType === 'property') {
if (bindingName === 'conditionExpression') {
propertyValue = bpmnFactory.create('bpmn:FormalExpression', {
body: value,
language: binding.scriptFormat
});
} else {
var moddlePropertyDescriptor = bo.$descriptor.propertiesByName[bindingName];
var moddleType = moddlePropertyDescriptor.type;
// make sure we only update String, Integer, Real and
// Boolean properties (do not accidentally override complex objects...)
if (BASIC_MODDLE_TYPES.indexOf(moddleType) === -1) {
throw new Error('cannot set moddle type <' + moddleType + '>');
}
if (moddleType === 'Boolean') {
propertyValue = !!value;
} else
if (moddleType === 'Integer') {
propertyValue = parseInt(value, 10);
if (isNaN(propertyValue)) {
// do not write NaN value
propertyValue = undefined;
}
} else {
propertyValue = value;
}
}
if (propertyValue !== undefined) {
updates.push(cmdHelper.updateBusinessObject(
element, bo, objectWithKey(bindingName, propertyValue)
));
}
}
var extensionElements;
if (EXTENSION_BINDING_TYPES.indexOf(bindingType) !== -1) {
extensionElements = bo.get('extensionElements');
// create extension elements, if they do not exist (yet)
if (!extensionElements) {
extensionElements = bpmnFactory.create('bpmn:ExtensionElements');
updates.push(cmdHelper.updateBusinessObject(
element, bo, objectWithKey('extensionElements', extensionElements)
));
}
}
// camunda:property
var camundaProperties,
existingCamundaProperty,
newCamundaProperty;
if (bindingType === CAMUNDA_PROPERTY_TYPE) {
camundaProperties = findExtension(extensionElements, 'camunda:Properties');
if (!camundaProperties) {
camundaProperties = bpmnFactory.create('camunda:Properties');
updates.push(cmdHelper.addElementsTolist(
element, extensionElements, 'values', [ camundaProperties ]
));
}
existingCamundaProperty = findCamundaProperty(camundaProperties, binding);
newCamundaProperty = createCamundaProperty(binding, value, bpmnFactory);
updates.push(cmdHelper.addAndRemoveElementsFromList(
element,
camundaProperties,
'values',
null,
[ newCamundaProperty ],
existingCamundaProperty ? [ existingCamundaProperty ] : []
));
}
// camunda:inputParameter
// camunda:outputParameter
var inputOutput,
existingIoParameter,
newIoParameter;
if (IO_BINDING_TYPES.indexOf(bindingType) !== -1) {
inputOutput = findExtension(extensionElements, 'camunda:InputOutput');
// create inputOutput element, if it do not exist (yet)
if (!inputOutput) {
inputOutput = bpmnFactory.create('camunda:InputOutput');
updates.push(cmdHelper.addElementsTolist(
element, extensionElements, 'values', inputOutput
));
}
}
if (bindingType === CAMUNDA_INPUT_PARAMETER_TYPE) {
existingIoParameter = findInputParameter(inputOutput, binding);
newIoParameter = createInputParameter(binding, value, bpmnFactory);
updates.push(cmdHelper.addAndRemoveElementsFromList(
element,
inputOutput,
'inputParameters',
null,
[ newIoParameter ],
existingIoParameter ? [ existingIoParameter ] : []
));
}
if (bindingType === CAMUNDA_OUTPUT_PARAMETER_TYPE) {
existingIoParameter = findOutputParameter(inputOutput, binding);
newIoParameter = createOutputParameter(binding, value, bpmnFactory);
updates.push(cmdHelper.addAndRemoveElementsFromList(
element,
inputOutput,
'outputParameters',
null,
[ newIoParameter ],
existingIoParameter ? [ existingIoParameter ] : []
));
}
// camunda:in
// camunda:out
// camunda:in:businessKey
var existingInOut,
newInOut;
if (IN_OUT_BINDING_TYPES.indexOf(bindingType) !== -1) {
existingInOut = findCamundaInOut(bo, binding);
if (bindingType === CAMUNDA_IN_TYPE) {
newInOut = createCamundaIn(binding, value, bpmnFactory);
} else
if (bindingType === CAMUNDA_OUT_TYPE) {
newInOut = createCamundaOut(binding, value, bpmnFactory);
} else {
newInOut = createCamundaInWithBusinessKey(binding, value, bpmnFactory);
}
updates.push(cmdHelper.addAndRemoveElementsFromList(
element,
extensionElements,
'values',
null,
[ newInOut ],
existingInOut ? [ existingInOut ] : []
));
}
if (updates.length) {
return updates;
}
// quick warning for better debugging
console.warn('no update', element, property, value);
}
/**
* Validate value of a given property.
*
* @param {String} value
* @param {PropertyDescriptor} property
*
* @return {Object} with validation errors
*/
function validateValue(value, property) {
var constraints = property.constraints || {};
if (constraints.notEmpty && isEmpty(value)) {
return 'Must not be empty';
}
if (constraints.maxLength && value.length > constraints.maxLength) {
return 'Must have max length ' + constraints.maxLength;
}
if (constraints.minLength && value.length < constraints.minLength) {
return 'Must have min length ' + constraints.minLength;
}
var pattern = constraints.pattern,
message;
if (pattern) {
if (typeof pattern !== 'string') {
message = pattern.message;
pattern = pattern.value;
}
if (!matchesPattern(value, pattern)) {
return message || 'Must match pattern ' + pattern;
}
}
}
//////// misc helpers ///////////////////////////////
/**
* Return an object with a single key -> value association.
*
* @param {String} key
* @param {Any} value
*
* @return {Object}
*/
function objectWithKey(key, value) {
var obj = {};
obj[key] = value;
return obj;
}
/**
* Does the given string match the specified pattern?
*
* @param {String} str
* @param {String} pattern
*
* @return {Boolean}
*/
function matchesPattern(str, pattern) {
var regexp = new RegExp(pattern);
return regexp.test(str);
}
function isEmpty(str) {
return !str || /^\s*$/.test(str);
}
/**
* Create a new {@link Error} indicating an unknown
* property binding.
*
* @param {PropertyDescriptor} property
*
* @return {Error}
*/
function unknownPropertyBinding(property) {
var binding = property.binding;
return new Error('unknown binding: <' + binding.type + '>');
}