bpmn-js/lib/features/modeling/cmd/UpdatePropertiesHandler.js

265 lines
6.4 KiB
JavaScript

'use strict';
var reduce = require('min-dash').reduce,
keys = require('min-dash').keys,
forEach = require('min-dash').forEach,
assign = require('min-dash').assign;
var getBusinessObject = require('../../../util/ModelUtil').getBusinessObject;
var TextUtil = require('diagram-js/lib/util/Text');
var DEFAULT_FLOW = 'default',
NAME = 'name',
ID = 'id',
DI = 'di';
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* A handler that implements a BPMN 2.0 property update.
*
* This should be used to set simple properties on elements with
* an underlying BPMN business object.
*
* Use respective diagram-js provided handlers if you would
* like to perform automated modeling.
*/
function UpdatePropertiesHandler(elementRegistry, moddle, translate, modeling) {
this._elementRegistry = elementRegistry;
this._moddle = moddle;
this._translate = translate;
this._modeling = modeling;
this._textUtil = new TextUtil();
}
UpdatePropertiesHandler.$inject = [ 'elementRegistry', 'moddle', 'translate', 'modeling' ];
module.exports = UpdatePropertiesHandler;
// api //////////////////////
/**
* Updates a BPMN element with a list of new properties
*
* @param {Object} context
* @param {djs.model.Base} context.element the element to update
* @param {Object} context.properties a list of properties to set on the element's
* businessObject (the BPMN model element)
*
* @return {Array<djs.model.Base>} the updated element
*/
UpdatePropertiesHandler.prototype.execute = function(context) {
var element = context.element,
changed = [ element ],
translate = this._translate;
if (!element) {
throw new Error(translate('element required'));
}
var elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
var businessObject = element.businessObject,
properties = unwrapBusinessObjects(context.properties),
oldProperties = context.oldProperties || getProperties(businessObject, properties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(businessObject[ID]);
elementRegistry.updateId(element, properties[ID]);
ids.claim(properties[ID], businessObject);
}
// correctly indicate visual changes on default flow updates
if (DEFAULT_FLOW in properties) {
if (properties[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
}
if (businessObject[DEFAULT_FLOW]) {
changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
}
}
if (NAME in properties && element.label) {
changed.push(element.label);
// show the label
element.label.hidden = !properties[NAME];
}
// update properties
setProperties(businessObject, properties);
// store old values
context.oldProperties = oldProperties;
context.changed = changed;
// indicate changed on objects affected by the update
return changed;
};
UpdatePropertiesHandler.prototype.postExecute = function(context) {
var element = context.element,
label = element.label;
var text = label && getBusinessObject(label).name;
if (!text) {
return;
}
// get layouted text bounds and resize external
// external label accordingly
var newLabelBounds = getLayoutedBounds(label, text, this._textUtil);
this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
};
/**
* Reverts the update on a BPMN elements properties.
*
* @param {Object} context
*
* @return {djs.model.Base} the updated element
*/
UpdatePropertiesHandler.prototype.revert = function(context) {
var element = context.element,
properties = context.properties,
oldProperties = context.oldProperties,
businessObject = element.businessObject,
elementRegistry = this._elementRegistry,
ids = this._moddle.ids;
// update properties
setProperties(businessObject, oldProperties);
if (isIdChange(properties, businessObject)) {
ids.unclaim(properties[ID]);
elementRegistry.updateId(element, oldProperties[ID]);
ids.claim(oldProperties[ID], businessObject);
}
return context.changed;
};
function isIdChange(properties, businessObject) {
return ID in properties && properties[ID] !== businessObject[ID];
}
function getProperties(businessObject, properties) {
var propertyNames = keys(properties);
return reduce(propertyNames, function(result, key) {
// handle DI seperately
if (key !== DI) {
result[key] = businessObject.get(key);
} else {
result[key] = getDiProperties(businessObject.di, keys(properties.di));
}
return result;
}, {});
}
function getDiProperties(di, propertyNames) {
return reduce(propertyNames, function(result, key) {
result[key] = di.get(key);
return result;
}, {});
}
function setProperties(businessObject, properties) {
forEach(properties, function(value, key) {
if (key !== DI) {
businessObject.set(key, value);
} else {
// only update, if businessObject.di exists
if (businessObject.di) {
setDiProperties(businessObject.di, value);
}
}
});
}
function setDiProperties(di, properties) {
forEach(properties, function(value, key) {
di.set(key, value);
});
}
var referencePropertyNames = [ 'default' ];
/**
* Make sure we unwrap the actual business object
* behind diagram element that may have been
* passed as arguments.
*
* @param {Object} properties
*
* @return {Object} unwrappedProps
*/
function unwrapBusinessObjects(properties) {
var unwrappedProps = assign({}, properties);
referencePropertyNames.forEach(function(name) {
if (name in properties) {
unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
}
});
return unwrappedProps;
}
// TODO(nikku): repeating code (search for <getLayoutedBounds>)
var EXTERNAL_LABEL_STYLE = {
fontFamily: 'Arial, sans-serif',
fontSize: '11px'
};
function getLayoutedBounds(bounds, text, textUtil) {
var layoutedLabelDimensions = textUtil.getDimensions(text, {
box: {
width: 90,
height: 30,
x: bounds.width / 2 + bounds.x,
y: bounds.height / 2 + bounds.y
},
style: EXTERNAL_LABEL_STYLE
});
// resize label shape to fit label text
return {
x: Math.round(bounds.x + bounds.width / 2 - layoutedLabelDimensions.width / 2),
y: Math.round(bounds.y),
width: Math.ceil(layoutedLabelDimensions.width),
height: Math.ceil(layoutedLabelDimensions.height)
};
}