'use strict'; var reduce = require('lodash/object/transform'), keys = require('lodash/object/keys'), forEach = require('lodash/collection/forEach'), assign = require('lodash/object/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} 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]; } if (DI in properties && businessObject.di) { setDiProperties(businessObject.di, properties.di); } // 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; if (DI in oldProperties && businessObject.di) { setDiProperties(businessObject.di, oldProperties.di); } // 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) { businessObject.set(key, 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 ) 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) }; }