feat(replace): add service that allows to replace elements
API - BpmnReplace#replaceElement see bpmn-io/bpmn-js#130
This commit is contained in:
parent
4fa01649b5
commit
3873709141
|
@ -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')
|
||||
|
|
|
@ -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') ||
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
__depends__: [
|
||||
require('diagram-js/lib/features/rules')
|
||||
],
|
||||
__init__: [ 'replace' ],
|
||||
replace: [ 'type', require('./BpmnReplace') ],
|
||||
};
|
|
@ -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>
|
|
@ -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();
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue