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:BPMNShape') ||
|
||||||
element.$instanceOf('bpmndi:BPMNEdge') ||
|
element.$instanceOf('bpmndi:BPMNEdge') ||
|
||||||
element.$instanceOf('bpmndi:BPMNDiagram') ||
|
element.$instanceOf('bpmndi:BPMNDiagram') ||
|
||||||
element.$instanceOf('bpmndi:BPMNPlane');
|
element.$instanceOf('bpmndi:BPMNPlane') ||
|
||||||
|
element.$instanceOf('bpmn:Property');
|
||||||
};
|
};
|
||||||
|
|
||||||
BpmnFactory.prototype._ensureId = function(element) {
|
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',
|
'appendBehavior',
|
||||||
'createBoundaryEventBehavior',
|
'createBoundaryEventBehavior',
|
||||||
'createDataObjectBehavior',
|
'createDataObjectBehavior',
|
||||||
'deleteLaneBehavior',
|
|
||||||
'createOnFlowBehavior',
|
'createOnFlowBehavior',
|
||||||
'createParticipantBehavior',
|
'createParticipantBehavior',
|
||||||
|
'dataInputAssociationBehavior',
|
||||||
|
'deleteLaneBehavior',
|
||||||
'modelingFeedback',
|
'modelingFeedback',
|
||||||
'removeParticipantBehavior',
|
'removeParticipantBehavior',
|
||||||
'replaceConnectionBehavior',
|
'replaceConnectionBehavior',
|
||||||
|
@ -16,9 +17,10 @@ module.exports = {
|
||||||
appendBehavior: [ 'type', require('./AppendBehavior') ],
|
appendBehavior: [ 'type', require('./AppendBehavior') ],
|
||||||
createBoundaryEventBehavior: [ 'type', require('./CreateBoundaryEventBehavior') ],
|
createBoundaryEventBehavior: [ 'type', require('./CreateBoundaryEventBehavior') ],
|
||||||
createDataObjectBehavior: [ 'type', require('./CreateDataObjectBehavior') ],
|
createDataObjectBehavior: [ 'type', require('./CreateDataObjectBehavior') ],
|
||||||
deleteLaneBehavior: [ 'type', require('./DeleteLaneBehavior') ],
|
|
||||||
createOnFlowBehavior: [ 'type', require('./CreateOnFlowBehavior') ],
|
createOnFlowBehavior: [ 'type', require('./CreateOnFlowBehavior') ],
|
||||||
createParticipantBehavior: [ 'type', require('./CreateParticipantBehavior') ],
|
createParticipantBehavior: [ 'type', require('./CreateParticipantBehavior') ],
|
||||||
|
dataInputAssociationBehavior: [ 'type', require('./DataInputAssociationBehavior') ],
|
||||||
|
deleteLaneBehavior: [ 'type', require('./DeleteLaneBehavior') ],
|
||||||
modelingFeedback: [ 'type', require('./ModelingFeedback') ],
|
modelingFeedback: [ 'type', require('./ModelingFeedback') ],
|
||||||
removeParticipantBehavior: [ 'type', require('./RemoveParticipantBehavior') ],
|
removeParticipantBehavior: [ 'type', require('./RemoveParticipantBehavior') ],
|
||||||
replaceConnectionBehavior: [ 'type', require('./ReplaceConnectionBehavior') ],
|
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