feat(replace): add service that allows to replace elements

API
- BpmnReplace#replaceElement

see bpmn-io/bpmn-js#130
This commit is contained in:
jdotzki 2015-01-19 12:13:39 +01:00
parent 4fa01649b5
commit 3873709141
6 changed files with 545 additions and 0 deletions

View File

@ -67,6 +67,8 @@ Modeler.prototype._modelingModules = [
require('diagram-js/lib/features/bendpoints'),
require('diagram-js/lib/features/resize'),
require('diagram-js/lib/features/lasso-tool'),
require('diagram-js/lib/features/replace'),
require('./features/replace'),
require('./features/modeling'),
require('./features/context-pad'),
require('./features/palette')

View File

@ -27,6 +27,11 @@ ModelingRules.prototype.init = function() {
return a === b;
}
function isEventConnectionValid(sourceBo, targetBo) {
return targetBo.$instanceOf('bpmn:StartEvent') ||
sourceBo.$instanceOf('bpmn:EndEvent');
}
// rules
function canConnect(source, target, connection) {
@ -63,6 +68,13 @@ ModelingRules.prototype.init = function() {
}
}
// Do not allow incoming connections on StartEvents
// and outgoing connections on EndEvents
if (isEventConnectionValid(sourceBo, targetBo)) {
return false;
}
return (sourceBo.$instanceOf('bpmn:FlowNode') ||
sourceBo.$instanceOf('bpmn:TextAnnotation')) &&
(targetBo.$instanceOf('bpmn:FlowNode') ||

View File

@ -0,0 +1,55 @@
'use strict';
var _ = require('lodash');
var LabelUtil = require('../label-editing/LabelUtil'),
BaseReplace = require('diagram-js/lib/features/replace/Replace');
function BpmnReplace(modeling, eventBus) {
BaseReplace.call(this, modeling);
this._originalReplaceElement = BaseReplace.prototype.replaceElement;
this._modeling = modeling;
eventBus.on([
'commandStack.shape.replace.postExecute'
], function(event) {
var context = event.context,
oldShape = context.oldShape,
newShape = context.newShape;
modeling.updateLabel(newShape, LabelUtil.getLabel(oldShape));
});
// TODO(nre): update bpmn specific properties based on our meta-model
// i.e. we should not only keep the name, but also
// other properties that exist in BOTH the old and new shape
}
module.exports = BpmnReplace;
BpmnReplace.$inject = [ 'modeling', 'eventBus' ];
BpmnReplace.prototype = Object.create(BaseReplace.prototype);
BpmnReplace.prototype.replaceElement = function(oldElement, newElementData) {
if (oldElement.waypoints) {
throw new Error('connections cannot be replaced (yet)');
}
// use old elements size for activities
// TODO(nre): may also specify do this during the replace suggestions
// already (i.e. replace suggestions = { type, label, newElementData }) (?)
if (oldElement.businessObject.$instanceOf('bpmn:Activity')) {
// TODO need also to respect min/max Size
newElementData.width = oldElement.width;
newElementData.height = oldElement.height;
}
return this._originalReplaceElement(oldElement, newElementData);
};

View File

@ -0,0 +1,7 @@
module.exports = {
__depends__: [
require('diagram-js/lib/features/rules')
],
__init__: [ 'replace' ],
replace: [ 'type', require('./BpmnReplace') ],
};

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
id="Definitions_1"
targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0fn1a6r</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1" name="Task Caption">
<bpmn:incoming>SequenceFlow_0fn1a6r</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0fg7s2y</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_0fn1a6r" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:exclusiveGateway id="ExclusiveGateway_1">
<bpmn:incoming>SequenceFlow_0fg7s2y</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0rxcijf</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="SequenceFlow_0fg7s2y" sourceRef="Task_1" targetRef="ExclusiveGateway_1" />
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_0rxcijf</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0rxcijf" sourceRef="ExclusiveGateway_1" targetRef="EndEvent_1" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="171" y="171" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="144" y="207" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="346" y="149" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0fn1a6r_di" bpmnElement="SequenceFlow_0fn1a6r">
<di:waypoint xsi:type="dc:Point" x="207" y="189" />
<di:waypoint xsi:type="dc:Point" x="346" y="189" />
<bpmndi:BPMNLabel>
<dc:Bounds x="311" y="179" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="ExclusiveGateway_1_di" bpmnElement="ExclusiveGateway_1">
<dc:Bounds x="573" y="164" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="553" y="214" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0fg7s2y_di" bpmnElement="SequenceFlow_0fg7s2y">
<di:waypoint xsi:type="dc:Point" x="446" y="189" />
<di:waypoint xsi:type="dc:Point" x="573" y="189" />
<bpmndi:BPMNLabel>
<dc:Bounds x="650" y="179" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
<dc:Bounds x="768" y="171" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="741" y="207" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0rxcijf_di" bpmnElement="SequenceFlow_0rxcijf">
<di:waypoint xsi:type="dc:Point" x="623" y="189" />
<di:waypoint xsi:type="dc:Point" x="768" y="189" />
<bpmndi:BPMNLabel>
<dc:Bounds x="789.5" y="179" width="90" height="20" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,394 @@
'use strict';
var TestHelper = require('../../../TestHelper');
/* global bootstrapModeler, inject */
var _ = require('lodash');
var fs = require('fs');
var modelingModule = require('../../../../lib/features/modeling'),
replaceModule = require('../../../../lib/features/replace'),
coreModule = require('../../../../lib/core');
describe('features/replace', function() {
var diagramXML = fs.readFileSync('test/fixtures/bpmn/features/bpmn-replace/01_replace.bpmn', 'utf8');
var testModules = [ coreModule, modelingModule, replaceModule ];
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('should replace', function() {
it('task', inject(function(elementRegistry, modeling, replace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).toBeDefined();
expect(businessObject.$instanceOf('bpmn:UserTask')).toBe(true);
}));
it('gateway', inject(function(elementRegistry, modeling, replace) {
// given
var gateway = elementRegistry.get('ExclusiveGateway_1');
var newElementData = {
type: 'bpmn:InclusiveGateway'
};
// when
var newElement = replace.replaceElement(gateway, newElementData);
// then
var businessObject = newElement.businessObject;
expect(newElement).toBeDefined();
expect(businessObject.$instanceOf('bpmn:InclusiveGateway')).toBe(true);
}));
});
describe('position and size', function() {
it('should keep position', inject(function(elementRegistry, replace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
expect(newElement.x).toBe(346);
expect(newElement.y).toBe(149);
}));
});
describe('label', function() {
it('should keep copy label', inject(function(elementRegistry, replace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
expect(newElement.businessObject.name).toBe('Task Caption');
}));
});
describe('undo support', function() {
it('should undo replace', inject(function(elementRegistry, modeling, replace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
replace.replaceElement(task, newElementData);
// when
commandStack.undo();
// then
var target = elementRegistry.get('Task_1'),
businessObject = target.businessObject;
expect(target).toBeDefined();
expect(businessObject.$instanceOf('bpmn:Task')).toBe(true);
}));
it('should redo replace', inject(function(elementRegistry, modeling, replace, commandStack, eventBus) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
var newElementData2 = {
type: 'bpmn:ServiceTask'
};
var usertask = replace.replaceElement(task, newElementData);
var servicetask = replace.replaceElement(usertask, newElementData2);
commandStack.undo();
commandStack.undo();
// when
commandStack.redo();
commandStack.redo();
// then
var businessObject = servicetask.businessObject;
expect(servicetask).toBeDefined();
expect(businessObject.$instanceOf('bpmn:ServiceTask')).toBe(true);
}));
});
describe('connection handling', function() {
it('should reconnect valid connections', inject(function(elementRegistry, modeling, replace) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).toBeDefined();
expect(outgoing).toBeDefined();
expect(source).toBe(elementRegistry.get('StartEvent_1'));
expect(target).toBe(elementRegistry.get('ExclusiveGateway_1'));
}));
it('should remove invalid incomming connections', inject(function(elementRegistry, modeling, replace) {
// given
var task = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).toBeUndefined();
expect(outgoing).toBeUndefined();
}));
it('should remove invalid outgoing connections', inject(function(elementRegistry, modeling, replace) {
// given
var task = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
// when
var newElement = replace.replaceElement(task, newElementData);
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).toBeUndefined();
expect(outgoing).toBeUndefined();
}));
describe('undo support', function() {
it('should reconnect valid connections', inject(function(elementRegistry, modeling, replace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
var newElement = replace.replaceElement(task, newElementData);
// when
commandStack.undo();
// then
var newTask = elementRegistry.get('Task_1');
var incoming = newTask.incoming[0],
outgoing = newTask.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).toBeDefined();
expect(outgoing).toBeDefined();
expect(source).toBe(elementRegistry.get('StartEvent_1'));
expect(target).toBe(elementRegistry.get('ExclusiveGateway_1'));
}));
it('should remove invalid incoming connections', inject(function(elementRegistry,
modeling, replace, commandStack) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
var newElement = replace.replaceElement(startEvent, newElementData);
// when
commandStack.undo();
// then
var newEvent = elementRegistry.get('StartEvent_1');
var incoming = newEvent.incoming[0],
outgoing = newEvent.outgoing[0],
target = outgoing.target;
expect(incoming).toBeUndefined();
expect(outgoing).toBeDefined();
expect(target).toBe(elementRegistry.get('Task_1'));
}));
it('should remove invalid outgoing connections', inject(function(elementRegistry,
modeling, replace, commandStack) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
var newElement = replace.replaceElement(endEvent, newElementData);
// when
commandStack.undo();
// then
var newEvent = elementRegistry.get('EndEvent_1');
var incoming = newEvent.incoming[0],
outgoing = newEvent.outgoing[0],
source = incoming.source;
expect(incoming).toBeDefined();
expect(outgoing).toBeUndefined();
expect(source).toBe(elementRegistry.get('ExclusiveGateway_1'));
}));
});
describe('redo support', function() {
it('should reconnect valid connections', inject(function(elementRegistry, modeling, replace, commandStack) {
// given
var task = elementRegistry.get('Task_1');
var newElementData = {
type: 'bpmn:UserTask'
};
var newElement = replace.replaceElement(task, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0],
source = incoming.source,
target = outgoing.target;
expect(incoming).toBeDefined();
expect(outgoing).toBeDefined();
expect(source).toBe(elementRegistry.get('StartEvent_1'));
expect(target).toBe(elementRegistry.get('ExclusiveGateway_1'));
}));
it('should remove invalid incoming connections', inject(function(elementRegistry,
modeling, replace, commandStack) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var newElementData = {
type: 'bpmn:EndEvent'
};
var newElement = replace.replaceElement(startEvent, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).toBeUndefined();
expect(outgoing).toBeUndefined();
}));
it('should remove invalid outgoing connections', inject(function(elementRegistry,
modeling, replace, commandStack) {
// given
var endEvent = elementRegistry.get('EndEvent_1');
var newElementData = {
type: 'bpmn:StartEvent'
};
var newElement = replace.replaceElement(endEvent, newElementData);
// when
commandStack.undo();
commandStack.redo();
// then
var incoming = newElement.incoming[0],
outgoing = newElement.outgoing[0];
expect(incoming).toBeUndefined();
expect(outgoing).toBeUndefined();
}));
});
});
});