feat(modeling/behaviors): add GroupBehavior

* Create new Category + Value for every new Group
* Cleanup on Group deletion
This commit is contained in:
Niklas Kiefer 2019-05-14 14:51:29 +02:00
parent 2dfeee7567
commit a7e3980059
4 changed files with 504 additions and 0 deletions

View File

@ -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);

View File

@ -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 ],

View File

@ -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>

View File

@ -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
);
}));
});
});
});