fix(modeling): correctly populate DataInputAssociation#targetRef
Closes #431
This commit is contained in:
parent
f89fd529de
commit
9ac0a9a957
|
@ -25,7 +25,8 @@ BpmnFactory.prototype._needsId = function(element) {
|
|||
element.$instanceOf('bpmndi:BPMNShape') ||
|
||||
element.$instanceOf('bpmndi:BPMNEdge') ||
|
||||
element.$instanceOf('bpmndi:BPMNDiagram') ||
|
||||
element.$instanceOf('bpmndi:BPMNPlane');
|
||||
element.$instanceOf('bpmndi:BPMNPlane') ||
|
||||
element.$instanceOf('bpmn:Property');
|
||||
};
|
||||
|
||||
BpmnFactory.prototype._ensureId = function(element) {
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
'use strict';
|
||||
|
||||
var inherits = require('inherits');
|
||||
|
||||
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
|
||||
|
||||
var Collections = require('diagram-js/lib/util/Collections');
|
||||
|
||||
var find = require('lodash/collection/find');
|
||||
|
||||
var is = require('../../../util/ModelUtil').is;
|
||||
|
||||
var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
|
||||
|
||||
|
||||
/**
|
||||
* This behavior makes sure we always set a fake
|
||||
* DataInputAssociation#targetRef as demanded by the BPMN 2.0
|
||||
* XSD schema.
|
||||
*
|
||||
* The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
|
||||
* which is created on the fly and cleaned up afterwards if not needed
|
||||
* anymore.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
*/
|
||||
function DataInputAssociationBehavior(eventBus, bpmnFactory) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
this.executed([
|
||||
'connection.create',
|
||||
'connection.delete',
|
||||
'connection.move',
|
||||
'connection.reconnectEnd'
|
||||
], ifDataInputAssociation(fixTargetRef));
|
||||
|
||||
this.reverted([
|
||||
'connection.create',
|
||||
'connection.delete',
|
||||
'connection.move',
|
||||
'connection.reconnectEnd'
|
||||
], ifDataInputAssociation(fixTargetRef));
|
||||
|
||||
|
||||
function usesTargetRef(element, targetRef, removedConnection) {
|
||||
|
||||
var inputAssociations = element.get('dataInputAssociations');
|
||||
|
||||
return find(inputAssociations, function(association) {
|
||||
return association !== removedConnection &&
|
||||
association.targetRef === targetRef;
|
||||
});
|
||||
}
|
||||
|
||||
function getTargetRef(element, create) {
|
||||
|
||||
var properties = element.get('properties');
|
||||
|
||||
var targetRefProp = find(properties, function(p) {
|
||||
return p.name === TARGET_REF_PLACEHOLDER_NAME;
|
||||
});
|
||||
|
||||
if (!targetRefProp && create) {
|
||||
targetRefProp = bpmnFactory.create('bpmn:Property', {
|
||||
name: TARGET_REF_PLACEHOLDER_NAME
|
||||
});
|
||||
|
||||
Collections.add(properties, targetRefProp);
|
||||
}
|
||||
|
||||
return targetRefProp;
|
||||
}
|
||||
|
||||
function cleanupTargetRef(element, connection) {
|
||||
|
||||
var targetRefProp = getTargetRef(element);
|
||||
|
||||
if (!targetRefProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!usesTargetRef(element, targetRefProp, connection)) {
|
||||
Collections.remove(element.get('properties'), targetRefProp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure targetRef is set to a valid property or
|
||||
* `null` if the connection is detached.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function fixTargetRef(event) {
|
||||
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
connectionBo = connection.businessObject,
|
||||
target = connection.target,
|
||||
targetBo = target && target.businessObject,
|
||||
newTarget = context.newTarget,
|
||||
newTargetBo = newTarget && newTarget.businessObject,
|
||||
oldTarget = context.oldTarget || context.target,
|
||||
oldTargetBo = oldTarget && oldTarget.businessObject;
|
||||
|
||||
var dataAssociation = connection.businessObject,
|
||||
targetRefProp;
|
||||
|
||||
if (oldTargetBo && oldTargetBo !== targetBo) {
|
||||
cleanupTargetRef(oldTargetBo, connectionBo);
|
||||
}
|
||||
|
||||
if (newTargetBo && newTargetBo !== targetBo) {
|
||||
cleanupTargetRef(newTargetBo, connectionBo);
|
||||
}
|
||||
|
||||
if (targetBo) {
|
||||
targetRefProp = getTargetRef(targetBo, true);
|
||||
dataAssociation.targetRef = targetRefProp;
|
||||
} else {
|
||||
dataAssociation.targetRef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataInputAssociationBehavior.$inject = [ 'eventBus', 'bpmnFactory' ];
|
||||
|
||||
inherits(DataInputAssociationBehavior, CommandInterceptor);
|
||||
|
||||
module.exports = DataInputAssociationBehavior;
|
||||
|
||||
|
||||
/**
|
||||
* Only call the given function when the event
|
||||
* touches a bpmn:DataInputAssociation.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
*/
|
||||
function ifDataInputAssociation(fn) {
|
||||
|
||||
return function(event) {
|
||||
var context = event.context,
|
||||
connection = context.connection;
|
||||
|
||||
if (is(connection, 'bpmn:DataInputAssociation')) {
|
||||
return fn(event);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,9 +3,10 @@ module.exports = {
|
|||
'appendBehavior',
|
||||
'createBoundaryEventBehavior',
|
||||
'createDataObjectBehavior',
|
||||
'deleteLaneBehavior',
|
||||
'createOnFlowBehavior',
|
||||
'createParticipantBehavior',
|
||||
'dataInputAssociationBehavior',
|
||||
'deleteLaneBehavior',
|
||||
'modelingFeedback',
|
||||
'removeParticipantBehavior',
|
||||
'replaceConnectionBehavior',
|
||||
|
@ -16,9 +17,10 @@ module.exports = {
|
|||
appendBehavior: [ 'type', require('./AppendBehavior') ],
|
||||
createBoundaryEventBehavior: [ 'type', require('./CreateBoundaryEventBehavior') ],
|
||||
createDataObjectBehavior: [ 'type', require('./CreateDataObjectBehavior') ],
|
||||
deleteLaneBehavior: [ 'type', require('./DeleteLaneBehavior') ],
|
||||
createOnFlowBehavior: [ 'type', require('./CreateOnFlowBehavior') ],
|
||||
createParticipantBehavior: [ 'type', require('./CreateParticipantBehavior') ],
|
||||
dataInputAssociationBehavior: [ 'type', require('./DataInputAssociationBehavior') ],
|
||||
deleteLaneBehavior: [ 'type', require('./DeleteLaneBehavior') ],
|
||||
modelingFeedback: [ 'type', require('./ModelingFeedback') ],
|
||||
removeParticipantBehavior: [ 'type', require('./RemoveParticipantBehavior') ],
|
||||
replaceConnectionBehavior: [ 'type', require('./ReplaceConnectionBehavior') ],
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?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" isExecutable="false">
|
||||
<bpmn:Task id="Task_A" name="Task_A">
|
||||
<bpmn:dataInputAssociation id="DataInputAssociation">
|
||||
<bpmn:sourceRef>DataObjectReference</bpmn:sourceRef>
|
||||
</bpmn:dataInputAssociation>
|
||||
</bpmn:Task>
|
||||
<bpmn:dataObjectReference id="DataObjectReference" name="DataObjectReference" dataObjectRef="DataObject" />
|
||||
<bpmn:dataObject id="DataObject" />
|
||||
<bpmn:Task id="Task_B" name="Task_B" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process">
|
||||
<bpmndi:BPMNShape id="Task_A_di" bpmnElement="Task_A">
|
||||
<dc:Bounds x="249" y="22" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="DataObjectReference_di" bpmnElement="DataObjectReference">
|
||||
<dc:Bounds x="100" y="201" width="36" height="50" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="73" y="251" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_B_di" bpmnElement="Task_B">
|
||||
<dc:Bounds x="249" y="186" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="DataInputAssociation_di" bpmnElement="DataInputAssociation">
|
||||
<di:waypoint xsi:type="dc:Point" x="136" y="210" />
|
||||
<di:waypoint xsi:type="dc:Point" x="257" y="100" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,147 @@
|
|||
'use strict';
|
||||
|
||||
require('../../../../TestHelper');
|
||||
|
||||
/* global inject, bootstrapModeler */
|
||||
|
||||
var find = require('lodash/collection/find');
|
||||
|
||||
var modelingModule = require('../../../../../lib/features/modeling');
|
||||
|
||||
|
||||
describe('modeling/behavior - fix DataInputAssociation#targetRef', function(){
|
||||
|
||||
var diagramXML = require('./DataInputAssociationBehavior.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, { modules: modelingModule }));
|
||||
|
||||
|
||||
it('should add on connect', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var dataObjectShape = elementRegistry.get('DataObjectReference'),
|
||||
taskShape = elementRegistry.get('Task_B');
|
||||
|
||||
|
||||
// when
|
||||
var newConnection = modeling.connect(dataObjectShape, taskShape, {
|
||||
type: 'bpmn:DataInputAssociation'
|
||||
});
|
||||
|
||||
var dataInputAssociation = newConnection.businessObject;
|
||||
|
||||
// then
|
||||
expect(dataInputAssociation.targetRef).to.exist;
|
||||
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(taskShape));
|
||||
}));
|
||||
|
||||
|
||||
it('should remove on connect / undo', inject(function(modeling, elementRegistry, commandStack) {
|
||||
|
||||
// given
|
||||
var dataObjectShape = elementRegistry.get('DataObjectReference'),
|
||||
taskShape = elementRegistry.get('Task_B');
|
||||
|
||||
var newConnection = modeling.connect(dataObjectShape, taskShape, {
|
||||
type: 'bpmn:DataInputAssociation'
|
||||
});
|
||||
|
||||
var dataInputAssociation = newConnection.businessObject;
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(dataInputAssociation.targetRef).to.not.exist;
|
||||
expect(getTargetRefProp(taskShape)).to.not.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should update on reconnectEnd', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var oldTarget = elementRegistry.get('Task_A'),
|
||||
connection = elementRegistry.get('DataInputAssociation'),
|
||||
dataInputAssociation = connection.businessObject,
|
||||
newTarget = elementRegistry.get('Task_B');
|
||||
|
||||
// when
|
||||
modeling.reconnectEnd(connection, newTarget, { x: newTarget.x, y: newTarget.y });
|
||||
|
||||
// then
|
||||
expect(getTargetRefProp(oldTarget)).not.to.exist;
|
||||
|
||||
expect(dataInputAssociation.targetRef).to.exist;
|
||||
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(newTarget));
|
||||
}));
|
||||
|
||||
|
||||
it('should update on reconnectEnd / undo', inject(function(modeling, elementRegistry, commandStack) {
|
||||
|
||||
// given
|
||||
var oldTarget = elementRegistry.get('Task_A'),
|
||||
connection = elementRegistry.get('DataInputAssociation'),
|
||||
dataInputAssociation = connection.businessObject,
|
||||
newTarget = elementRegistry.get('Task_B');
|
||||
|
||||
modeling.reconnectEnd(connection, newTarget, { x: newTarget.x, y: newTarget.y });
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(getTargetRefProp(newTarget)).not.to.exist;
|
||||
|
||||
expect(dataInputAssociation.targetRef).to.exist;
|
||||
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(oldTarget));
|
||||
}));
|
||||
|
||||
|
||||
it('should unset on remove', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var oldTarget = elementRegistry.get('Task_A'),
|
||||
connection = elementRegistry.get('DataInputAssociation'),
|
||||
dataInputAssociation = connection.businessObject;
|
||||
|
||||
// when
|
||||
modeling.removeElements([ connection ]);
|
||||
|
||||
// then
|
||||
expect(getTargetRefProp(oldTarget)).not.to.exist;
|
||||
|
||||
expect(dataInputAssociation.targetRef).to.not.exist;
|
||||
}));
|
||||
|
||||
|
||||
it('should unset on remove / undo', inject(function(modeling, elementRegistry, commandStack) {
|
||||
|
||||
// given
|
||||
var oldTarget = elementRegistry.get('Task_A'),
|
||||
connection = elementRegistry.get('DataInputAssociation'),
|
||||
dataInputAssociation = connection.businessObject;
|
||||
|
||||
modeling.removeElements([ connection ]);
|
||||
|
||||
// when
|
||||
commandStack.undo();
|
||||
|
||||
// then
|
||||
expect(dataInputAssociation.targetRef).to.exist;
|
||||
expect(dataInputAssociation.targetRef).to.eql(getTargetRefProp(oldTarget));
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getTargetRefProp(element) {
|
||||
|
||||
expect(element).to.exist;
|
||||
|
||||
var properties = element.businessObject.get('properties');
|
||||
|
||||
return find(properties, function(p) {
|
||||
return p.name === '__targetRef_placeholder';
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue