feat(modeling): add property update mechanism
This adds the modeling#updateProperties(element, props) method to the modeler that can be used to set BPMN 2.0 properties on elements. By assigning the properties this way, the modeler is aware of the elements that got changed and can update / redraw the elements accordingly. This hooks up with the modelers undo/redo chain, too. Related to #167
This commit is contained in:
parent
07ba58d805
commit
2019c658df
|
@ -10,6 +10,8 @@ var CreateShapeHandler = require('diagram-js/lib/features/modeling/cmd/CreateSha
|
|||
MoveShapesHandler = require('diagram-js/lib/features/modeling/cmd/MoveShapesHandler'),
|
||||
ResizeShapeHandler = require('diagram-js/lib/features/modeling/cmd/ResizeShapeHandler'),
|
||||
|
||||
UpdatePropertiesHandler = require('./cmd/UpdatePropertiesHandler'),
|
||||
|
||||
AppendShapeHandler = require('diagram-js/lib/features/modeling/cmd/AppendShapeHandler'),
|
||||
|
||||
CreateLabelHandler = require('diagram-js/lib/features/modeling/cmd/CreateLabelHandler'),
|
||||
|
@ -49,6 +51,8 @@ Modeling.prototype.registerHandlers = function(commandStack) {
|
|||
|
||||
commandStack.registerHandler('label.create', CreateLabelHandler);
|
||||
|
||||
commandStack.registerHandler('element.updateProperties', UpdatePropertiesHandler);
|
||||
|
||||
commandStack.registerHandler('connection.create', CreateConnectionHandler);
|
||||
commandStack.registerHandler('connection.delete', DeleteConnectionHandler);
|
||||
commandStack.registerHandler('connection.move', MoveConnectionHandler);
|
||||
|
@ -83,3 +87,11 @@ Modeling.prototype.connect = function(source, target, attrs) {
|
|||
|
||||
return this.createConnection(source, target, attrs, source.parent);
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.updateProperties = function(element, properties) {
|
||||
this._commandStack.execute('element.updateProperties', {
|
||||
element: element,
|
||||
properties: properties
|
||||
});
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
'use strict';
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
var DEFAULT_FLOW = 'default',
|
||||
NAME = 'name';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this._elementRegistry = elementRegistry;
|
||||
}
|
||||
|
||||
UpdatePropertiesHandler.$inject = [ 'elementRegistry' ];
|
||||
|
||||
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.mode.Base>} the updated element
|
||||
*/
|
||||
UpdatePropertiesHandler.prototype.execute = function(context) {
|
||||
|
||||
var element = context.element,
|
||||
changed = [ element ];
|
||||
|
||||
if (!element) {
|
||||
throw new Error('element required');
|
||||
}
|
||||
|
||||
var elementRegistry = this._elementRegistry;
|
||||
|
||||
var businessObject = element.businessObject,
|
||||
properties = context.properties,
|
||||
oldProperties = context.oldProperties || _.pick(businessObject, _.keys(properties));
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// update properties
|
||||
_.assign(businessObject, properties);
|
||||
|
||||
|
||||
// store old values
|
||||
context.oldProperties = oldProperties;
|
||||
context.changed = changed;
|
||||
|
||||
// indicate changed on objects affected by the update
|
||||
return changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the update on a BPMN elements properties.
|
||||
*
|
||||
* @param {Object} context
|
||||
*
|
||||
* @return {djs.mode.Base} the updated element
|
||||
*/
|
||||
UpdatePropertiesHandler.prototype.revert = function(context) {
|
||||
|
||||
var element = context.element,
|
||||
businessObject = element.businessObject;
|
||||
|
||||
_.assign(businessObject, context.oldProperties);
|
||||
|
||||
return context.changed;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="_G5HDsJKJEeSY3uHQ7B6T_A" exporter="camunda modeler" exporterVersion="2.6.0" targetNamespace="http://activiti.org/bpmn">
|
||||
<bpmn2:process id="Process_1" isExecutable="false">
|
||||
<bpmn2:serviceTask id="ServiceTask_1">
|
||||
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
|
||||
<bpmn2:outgoing>SequenceFlow_3</bpmn2:outgoing>
|
||||
<bpmn2:outgoing>SequenceFlow_4</bpmn2:outgoing>
|
||||
</bpmn2:serviceTask>
|
||||
<bpmn2:sequenceFlow id="SequenceFlow_3" name="conditional" sourceRef="ServiceTask_1" targetRef="EndEvent_1">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">${foo > bar}</bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
<bpmn2:task id="Task_2">
|
||||
<bpmn2:incoming>SequenceFlow_2</bpmn2:incoming>
|
||||
<bpmn2:incoming>SequenceFlow_4</bpmn2:incoming>
|
||||
</bpmn2:task>
|
||||
<bpmn2:exclusiveGateway id="ExclusiveGateway_1" default="SequenceFlow_1">
|
||||
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
|
||||
<bpmn2:outgoing>SequenceFlow_2</bpmn2:outgoing>
|
||||
</bpmn2:exclusiveGateway>
|
||||
<bpmn2:sequenceFlow id="SequenceFlow_1" name="default" sourceRef="ExclusiveGateway_1" targetRef="ServiceTask_1"/>
|
||||
<bpmn2:sequenceFlow id="SequenceFlow_2" name="" sourceRef="ExclusiveGateway_1" targetRef="Task_2">
|
||||
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression"><![CDATA[${foo < bar}]]></bpmn2:conditionExpression>
|
||||
</bpmn2:sequenceFlow>
|
||||
<bpmn2:sequenceFlow id="SequenceFlow_4" name="" sourceRef="ServiceTask_1" targetRef="Task_2"/>
|
||||
<bpmn2:endEvent id="EndEvent_1">
|
||||
<bpmn2:incoming>SequenceFlow_3</bpmn2:incoming>
|
||||
</bpmn2:endEvent>
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_ExclusiveGateway_2" bpmnElement="ExclusiveGateway_1" isMarkerVisible="true">
|
||||
<dc:Bounds height="50.0" width="50.0" x="372.0" y="204.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="0.0" width="0.0" x="397.0" y="259.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Task_2" bpmnElement="ServiceTask_1">
|
||||
<dc:Bounds height="80.0" width="100.0" x="492.0" y="84.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1" sourceElement="_BPMNShape_ExclusiveGateway_2" targetElement="_BPMNShape_Task_2">
|
||||
<di:waypoint xsi:type="dc:Point" x="397.0" y="204.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="397.0" y="124.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="492.0" y="124.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="21.0" width="44.0" x="348.0" y="138.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_2">
|
||||
<dc:Bounds height="80.0" width="100.0" x="492.0" y="300.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2" bpmnElement="SequenceFlow_2" sourceElement="_BPMNShape_ExclusiveGateway_2" targetElement="_BPMNShape_Task_3">
|
||||
<di:waypoint xsi:type="dc:Point" x="397.0" y="254.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="397.0" y="340.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="492.0" y="340.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="6.0" width="6.0" x="394.0" y="279.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_2" bpmnElement="EndEvent_1">
|
||||
<dc:Bounds height="36.0" width="36.0" x="732.0" y="106.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="0.0" width="0.0" x="750.0" y="147.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3" bpmnElement="SequenceFlow_3" sourceElement="_BPMNShape_Task_2" targetElement="_BPMNShape_EndEvent_2">
|
||||
<di:waypoint xsi:type="dc:Point" x="592.0" y="124.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="732.0" y="124.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="21.0" width="68.0" x="612.0" y="128.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_4" bpmnElement="SequenceFlow_4" sourceElement="_BPMNShape_Task_2" targetElement="_BPMNShape_Task_3">
|
||||
<di:waypoint xsi:type="dc:Point" x="542.0" y="164.0"/>
|
||||
<di:waypoint xsi:type="dc:Point" x="542.0" y="300.0"/>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>
|
|
@ -0,0 +1,205 @@
|
|||
'use strict';
|
||||
|
||||
var TestHelper = require('../../../TestHelper');
|
||||
|
||||
/* global bootstrapModeler, inject */
|
||||
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var modelingModule = require('../../../../lib/features/modeling'),
|
||||
coreModule = require('../../../../lib/core');
|
||||
|
||||
|
||||
describe('features/modeling - update properties', function() {
|
||||
|
||||
var diagramXML = fs.readFileSync('test/fixtures/bpmn/conditions.bpmn', 'utf8');
|
||||
|
||||
var testModules = [ coreModule, modelingModule ];
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
|
||||
|
||||
|
||||
var updatedElements;
|
||||
|
||||
beforeEach(inject(function(eventBus) {
|
||||
|
||||
eventBus.on([ 'commandStack.execute', 'commandStack.revert' ], function() {
|
||||
updatedElements = [];
|
||||
});
|
||||
|
||||
eventBus.on('element.changed', function(event) {
|
||||
updatedElements.push(event.element);
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
|
||||
describe('should execute', function() {
|
||||
|
||||
it('setting loop characteristics', inject(function(elementRegistry, modeling, moddle) {
|
||||
|
||||
// given
|
||||
var loopCharacteristics = moddle.create('bpmn:MultiInstanceLoopCharacteristics');
|
||||
|
||||
var taskShape = elementRegistry.get('ServiceTask_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(taskShape, { loopCharacteristics: loopCharacteristics });
|
||||
|
||||
// then
|
||||
expect(taskShape.businessObject.loopCharacteristics).toBe(loopCharacteristics);
|
||||
|
||||
|
||||
// task shape got updated
|
||||
expect(updatedElements).toContain(taskShape);
|
||||
}));
|
||||
|
||||
|
||||
it('updating default flow', inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var gatewayShape = elementRegistry.get('ExclusiveGateway_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(gatewayShape, { 'default': undefined });
|
||||
|
||||
// then
|
||||
expect(gatewayShape.businessObject['default']).not.toBeDefined();
|
||||
|
||||
// flow got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1'));
|
||||
}));
|
||||
|
||||
|
||||
it('updating label', inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var flowConnection = elementRegistry.get('SequenceFlow_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
|
||||
|
||||
// then
|
||||
expect(flowConnection.businessObject.name).toBe('FOO BAR');
|
||||
|
||||
// flow label got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1_label'));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('should undo', function() {
|
||||
|
||||
it('setting loop characteristics', inject(function(elementRegistry, modeling, commandStack, moddle) {
|
||||
|
||||
// given
|
||||
var loopCharactersistics = moddle.create('bpmn:MultiInstanceLoopCharacteristics');
|
||||
|
||||
var taskShape = elementRegistry.get('ServiceTask_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(taskShape, { loopCharacteristics: loopCharactersistics });
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(taskShape.businessObject.loopCharactersistics).not.toBeDefined();
|
||||
}));
|
||||
|
||||
|
||||
it('updating default flow', inject(function(elementRegistry, commandStack, modeling) {
|
||||
|
||||
// given
|
||||
var gatewayShape = elementRegistry.get('ExclusiveGateway_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(gatewayShape, { 'default': undefined });
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(gatewayShape.businessObject['default']).toBeDefined();
|
||||
|
||||
// flow got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1'));
|
||||
}));
|
||||
|
||||
|
||||
it('updating name', inject(function(elementRegistry, commandStack, modeling) {
|
||||
|
||||
// given
|
||||
var flowConnection = elementRegistry.get('SequenceFlow_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(flowConnection.businessObject.name).toBe('default');
|
||||
|
||||
// flow got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1_label'));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('should redo', function() {
|
||||
|
||||
it('setting loop characteristics', inject(function(elementRegistry, modeling, commandStack, moddle) {
|
||||
|
||||
// given
|
||||
var loopCharacteristics = moddle.create('bpmn:MultiInstanceLoopCharacteristics');
|
||||
|
||||
var taskShape = elementRegistry.get('ServiceTask_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(taskShape, { loopCharacteristics: loopCharacteristics });
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(taskShape.businessObject.loopCharacteristics).toBe(loopCharacteristics);
|
||||
}));
|
||||
|
||||
|
||||
it('updating default flow', inject(function(elementRegistry, commandStack, modeling) {
|
||||
|
||||
// given
|
||||
var gatewayShape = elementRegistry.get('ExclusiveGateway_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(gatewayShape, { 'default': undefined });
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(gatewayShape.businessObject['default']).not.toBeDefined();
|
||||
|
||||
// flow got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1'));
|
||||
}));
|
||||
|
||||
|
||||
it('updating name', inject(function(elementRegistry, commandStack, modeling) {
|
||||
|
||||
// given
|
||||
var flowConnection = elementRegistry.get('SequenceFlow_1');
|
||||
|
||||
// when
|
||||
modeling.updateProperties(flowConnection, { name: 'FOO BAR' });
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
// then
|
||||
expect(flowConnection.businessObject.name).toBe('FOO BAR');
|
||||
|
||||
// flow got updated, too
|
||||
expect(updatedElements).toContain(elementRegistry.get('SequenceFlow_1_label'));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue