feat(modeling/behaviors): add GroupBehavior
* Create new Category + Value for every new Group * Cleanup on Group deletion
This commit is contained in:
parent
2dfeee7567
commit
a7e3980059
|
@ -0,0 +1,192 @@
|
|||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
add as collectionAdd,
|
||||
remove as collectionRemove
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific Group behavior
|
||||
*/
|
||||
export default function GroupBehavior(eventBus, bpmnFactory, canvas, elementRegistry) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Gets process definitions
|
||||
*
|
||||
* @return {ModdleElement} definitions
|
||||
*/
|
||||
function getDefinitions() {
|
||||
var rootElement = canvas.getRootElement(),
|
||||
businessObject = getBusinessObject(rootElement);
|
||||
|
||||
return businessObject.$parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a referenced category value for a given group shape
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
*/
|
||||
function removeReferencedCategoryValue(shape) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValue = businessObject.categoryValueRef,
|
||||
category = categoryValue.$parent;
|
||||
|
||||
if (!categoryValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
collectionRemove(category.categoryValue, categoryValue);
|
||||
|
||||
// cleanup category if it is empty
|
||||
if (category && !category.categoryValue.length) {
|
||||
removeCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given category from the definitions
|
||||
*
|
||||
* @param {ModdleElement} category
|
||||
*/
|
||||
function removeCategory(category) {
|
||||
|
||||
var definitions = getDefinitions();
|
||||
|
||||
collectionRemove(definitions.get('rootElements'), category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all group element in the current registry
|
||||
*
|
||||
* @return {Array<djs.model.shape>} a list of group shapes
|
||||
*/
|
||||
function getGroupElements() {
|
||||
return elementRegistry.filter(function(e) {
|
||||
return is(e, 'bpmn:Group');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given categoryValue is referenced in one of the given elements
|
||||
*
|
||||
* @param {Array<djs.model.shape>} elements
|
||||
* @param {ModdleElement} categoryValue
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isReferenced(elements, categoryValue) {
|
||||
return elements.some(function(e) {
|
||||
|
||||
var businessObject = getBusinessObject(e);
|
||||
|
||||
return businessObject.categoryValueRef
|
||||
&& businessObject.categoryValueRef === categoryValue;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* remove referenced category + value when group was deleted
|
||||
*/
|
||||
this.executed('shape.delete', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValueRef = businessObject.categoryValueRef,
|
||||
groupElements = getGroupElements();
|
||||
|
||||
if (!isReferenced(groupElements, categoryValueRef)) {
|
||||
removeReferencedCategoryValue(shape);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* re-attach removed category
|
||||
*/
|
||||
this.reverted('shape.delete', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValueRef = businessObject.categoryValueRef,
|
||||
definitions = getDefinitions(),
|
||||
category = categoryValueRef ? categoryValueRef.$parent : null;
|
||||
|
||||
collectionAdd(category.get('categoryValue'), categoryValueRef);
|
||||
collectionAdd(definitions.get('rootElements'), category);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* create new category + value when group was created
|
||||
*/
|
||||
this.execute('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
businessObject = getBusinessObject(shape);
|
||||
|
||||
if (is(businessObject, 'bpmn:Group') && !businessObject.categoryValueRef) {
|
||||
|
||||
var definitions = getDefinitions();
|
||||
|
||||
var categoryValue = bpmnFactory.create('bpmn:CategoryValue'),
|
||||
category = bpmnFactory.create('bpmn:Category', {
|
||||
categoryValue: [ categoryValue ]
|
||||
});
|
||||
|
||||
// add to correct place
|
||||
collectionAdd(definitions.get('rootElements'), category);
|
||||
getBusinessObject(category).$parent = definitions;
|
||||
getBusinessObject(categoryValue).$parent = category;
|
||||
|
||||
// link the reference to the Group
|
||||
businessObject.categoryValueRef = categoryValue;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
this.revert('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
removeReferencedCategoryValue(shape);
|
||||
|
||||
delete getBusinessObject(shape).categoryValueRef;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
GroupBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory',
|
||||
'canvas',
|
||||
'elementRegistry'
|
||||
];
|
||||
|
||||
inherits(GroupBehavior, CommandInterceptor);
|
|
@ -10,6 +10,7 @@ import DataStoreBehavior from './DataStoreBehavior';
|
|||
import DeleteLaneBehavior from './DeleteLaneBehavior';
|
||||
import DropOnFlowBehavior from './DropOnFlowBehavior';
|
||||
import EventBasedGatewayBehavior from './EventBasedGatewayBehavior';
|
||||
import GroupBehavior from './GroupBehavior';
|
||||
import ImportDockingFix from './ImportDockingFix';
|
||||
import IsHorizontalFix from './IsHorizontalFix';
|
||||
import LabelBehavior from './LabelBehavior';
|
||||
|
@ -39,6 +40,7 @@ export default {
|
|||
'deleteLaneBehavior',
|
||||
'dropOnFlowBehavior',
|
||||
'eventBasedGatewayBehavior',
|
||||
'groupBehavior',
|
||||
'importDockingFix',
|
||||
'isHorizontalFix',
|
||||
'labelBehavior',
|
||||
|
@ -66,6 +68,7 @@ export default {
|
|||
deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
|
||||
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
|
||||
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
|
||||
groupBehavior: [ 'type', GroupBehavior ],
|
||||
importDockingFix: [ 'type', ImportDockingFix ],
|
||||
isHorizontalFix: [ 'type', IsHorizontalFix ],
|
||||
labelBehavior: [ 'type', LabelBehavior ],
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://www.signavio.com/bpmn20" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="Definition_1" name="" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.2.0-dev">
|
||||
<category id="Category_1">
|
||||
<categoryValue id="CategoryValue_1" value="Value 1" />
|
||||
<categoryValue id="CategoryValue_2" value="Value 2" />
|
||||
</category>
|
||||
<category id="Category_2">
|
||||
<categoryValue id="CategoryValue_3" value="Value 3" />
|
||||
</category>
|
||||
<process id="Process_1" isExecutable="false">
|
||||
<group id="Group_1" categoryValueRef="CategoryValue_1" />
|
||||
<group id="Group_2" categoryValueRef="CategoryValue_1" />
|
||||
<group id="Group_3" categoryValueRef="CategoryValue_2" />
|
||||
<group id="Group_4" categoryValueRef="CategoryValue_3" />
|
||||
</process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="Group_1_di" bpmnElement="Group_1">
|
||||
<omgdc:Bounds x="162" y="75" width="200" height="200" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<omgdc:Bounds x="276" y="98" width="58" height="18.96" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Group_2_di" bpmnElement="Group_2">
|
||||
<omgdc:Bounds x="387" y="75" width="200" height="200" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Group_3_di" bpmnElement="Group_3">
|
||||
<omgdc:Bounds x="609" y="75" width="200" height="200" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Group_4_di" bpmnElement="Group_4">
|
||||
<omgdc:Bounds x="836" y="75" width="200" height="200" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</definitions>
|
|
@ -0,0 +1,274 @@
|
|||
import {
|
||||
bootstrapModeler,
|
||||
inject
|
||||
} from 'test/TestHelper';
|
||||
|
||||
import {
|
||||
getBusinessObject
|
||||
} from 'lib/util/ModelUtil';
|
||||
|
||||
import {
|
||||
indexOf as collectionIndexOf
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import modelingModule from 'lib/features/modeling';
|
||||
import coreModule from 'lib/core';
|
||||
|
||||
|
||||
describe('features/modeling/behavior - groups', function() {
|
||||
|
||||
var testModules = [ coreModule, modelingModule ];
|
||||
|
||||
|
||||
var processDiagramXML = require('./GroupBehaviorSpec.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(processDiagramXML, { modules: testModules.concat(modelingModule) }));
|
||||
|
||||
function expectIncludedOrNot(collection, object, expected) {
|
||||
var isIncluded = collectionIndexOf(collection, object) >= 0;
|
||||
|
||||
expect(isIncluded).to.equal(expected);
|
||||
}
|
||||
|
||||
describe('creation', function() {
|
||||
|
||||
describe('should create new Category for every new Group', function() {
|
||||
|
||||
it('execute', inject(function(canvas, elementFactory, modeling) {
|
||||
|
||||
// given
|
||||
var group = elementFactory.createShape({ type: 'bpmn:Group' }),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
var groupShape = modeling.createShape(group, { x: 100, y: 100 }, root),
|
||||
categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
// then
|
||||
expect(categoryValueRef).to.exist;
|
||||
expect(category).to.exist;
|
||||
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
true
|
||||
);
|
||||
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
category,
|
||||
true
|
||||
);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('undo', inject(function(canvas, elementFactory, modeling, commandStack) {
|
||||
|
||||
// given
|
||||
var group = elementFactory.createShape({ type: 'bpmn:Group' }),
|
||||
root = canvas.getRootElement();
|
||||
|
||||
// when
|
||||
var groupShape = modeling.createShape(group, { x: 100, y: 100 }, root);
|
||||
|
||||
commandStack.undo();
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef;
|
||||
|
||||
// then
|
||||
expect(categoryValueRef).not.to.exist;
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('redo', inject(function(canvas, elementFactory, modeling, commandStack) {
|
||||
|
||||
// given
|
||||
var group = elementFactory.createShape({ type: 'bpmn:Group' }),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
var groupShape = modeling.createShape(group, { x: 100, y: 100 }, root);
|
||||
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
// then
|
||||
expect(categoryValueRef).to.exist;
|
||||
expect(categoryValueRef.$parent).to.exist;
|
||||
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
true
|
||||
);
|
||||
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
category,
|
||||
true
|
||||
);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('deletion', function() {
|
||||
|
||||
it('should NOT remove CategoryValue if it is still referenced somewhere', inject(
|
||||
function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var groupShape = elementRegistry.get('Group_1');
|
||||
|
||||
// when
|
||||
modeling.removeShape(groupShape);
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
// then
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
true
|
||||
);
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
it('should NOT remove Category if it still has CategoryValues', inject(
|
||||
function(canvas, elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var groupShape = elementRegistry.get('Group_3'),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
modeling.removeShape(groupShape);
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef;
|
||||
|
||||
// then
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
categoryValueRef.$parent,
|
||||
true
|
||||
);
|
||||
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
describe('should remove referenced Category + Value when Group was deleted', function() {
|
||||
|
||||
it('execute', inject(function(canvas, elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var groupShape = elementRegistry.get('Group_4'),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
modeling.removeShape(groupShape);
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
|
||||
// then
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
false
|
||||
);
|
||||
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
category,
|
||||
false
|
||||
);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('undo', inject(function(canvas, elementRegistry, modeling, commandStack) {
|
||||
|
||||
// given
|
||||
var groupShape = elementRegistry.get('Group_4'),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
modeling.removeShape(groupShape);
|
||||
|
||||
commandStack.undo();
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
// then
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
true
|
||||
);
|
||||
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
category,
|
||||
true
|
||||
);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('redo', inject(function(canvas, elementRegistry, modeling, commandStack) {
|
||||
|
||||
// given
|
||||
var groupShape = elementRegistry.get('Group_4'),
|
||||
root = canvas.getRootElement(),
|
||||
definitions = getBusinessObject(root).$parent;
|
||||
|
||||
// when
|
||||
modeling.removeShape(groupShape);
|
||||
|
||||
commandStack.undo();
|
||||
commandStack.redo();
|
||||
|
||||
var categoryValueRef = getBusinessObject(groupShape).categoryValueRef,
|
||||
category = categoryValueRef.$parent;
|
||||
|
||||
|
||||
// then
|
||||
expectIncludedOrNot(
|
||||
category.get('categoryValue'),
|
||||
categoryValueRef,
|
||||
false
|
||||
);
|
||||
|
||||
expectIncludedOrNot(
|
||||
definitions.get('rootElements'),
|
||||
category,
|
||||
false
|
||||
);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue