123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- import {
- assign,
- forEach
- } from 'min-dash';
- import inherits from 'inherits';
- import {
- remove as collectionRemove,
- add as collectionAdd
- } from 'diagram-js/lib/util/Collections';
- import {
- Label
- } from 'diagram-js/lib/model';
- import {
- getBusinessObject,
- is
- } from '../../util/ModelUtil';
- import {
- isAny
- } from './util/ModelingUtil';
- import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
- /**
- * A handler responsible for updating the underlying BPMN 2.0 XML + DI
- * once changes on the diagram happen
- */
- export default function BpmnUpdater(
- eventBus, bpmnFactory, connectionDocking,
- translate) {
- CommandInterceptor.call(this, eventBus);
- this._bpmnFactory = bpmnFactory;
- this._translate = translate;
- var self = this;
- // connection cropping //////////////////////
- // crop connection ends during create/update
- function cropConnection(e) {
- var context = e.context,
- hints = context.hints || {},
- connection;
- if (!context.cropped && hints.createElementsBehavior !== false) {
- connection = context.connection;
- connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
- context.cropped = true;
- }
- }
- this.executed([
- 'connection.layout',
- 'connection.create'
- ], cropConnection);
- this.reverted([ 'connection.layout' ], function(e) {
- delete e.context.cropped;
- });
- // BPMN + DI update //////////////////////
- // update parent
- function updateParent(e) {
- var context = e.context;
- self.updateParent(context.shape || context.connection, context.oldParent);
- }
- function reverseUpdateParent(e) {
- var context = e.context;
- var element = context.shape || context.connection,
- // oldParent is the (old) new parent, because we are undoing
- oldParent = context.parent || context.newParent;
- self.updateParent(element, oldParent);
- }
- this.executed([
- 'shape.move',
- 'shape.create',
- 'shape.delete',
- 'connection.create',
- 'connection.move',
- 'connection.delete'
- ], ifBpmn(updateParent));
- this.reverted([
- 'shape.move',
- 'shape.create',
- 'shape.delete',
- 'connection.create',
- 'connection.move',
- 'connection.delete'
- ], ifBpmn(reverseUpdateParent));
- /*
- * ## Updating Parent
- *
- * When morphing a Process into a Collaboration or vice-versa,
- * make sure that both the *semantic* and *di* parent of each element
- * is updated.
- *
- */
- function updateRoot(event) {
- var context = event.context,
- oldRoot = context.oldRoot,
- children = oldRoot.children;
- forEach(children, function(child) {
- if (is(child, 'bpmn:BaseElement')) {
- self.updateParent(child);
- }
- });
- }
- this.executed([ 'canvas.updateRoot' ], updateRoot);
- this.reverted([ 'canvas.updateRoot' ], updateRoot);
- // update bounds
- function updateBounds(e) {
- var shape = e.context.shape;
- if (!is(shape, 'bpmn:BaseElement')) {
- return;
- }
- self.updateBounds(shape);
- }
- this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
- // exclude labels because they're handled separately during shape.changed
- if (event.context.shape.type === 'label') {
- return;
- }
- updateBounds(event);
- }));
- this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
- // exclude labels because they're handled separately during shape.changed
- if (event.context.shape.type === 'label') {
- return;
- }
- updateBounds(event);
- }));
- // Handle labels separately. This is necessary, because the label bounds have to be updated
- // every time its shape changes, not only on move, create and resize.
- eventBus.on('shape.changed', function(event) {
- if (event.element.type === 'label') {
- updateBounds({ context: { shape: event.element } });
- }
- });
- // attach / detach connection
- function updateConnection(e) {
- self.updateConnection(e.context);
- }
- this.executed([
- 'connection.create',
- 'connection.move',
- 'connection.delete',
- 'connection.reconnect'
- ], ifBpmn(updateConnection));
- this.reverted([
- 'connection.create',
- 'connection.move',
- 'connection.delete',
- 'connection.reconnect'
- ], ifBpmn(updateConnection));
- // update waypoints
- function updateConnectionWaypoints(e) {
- self.updateConnectionWaypoints(e.context.connection);
- }
- this.executed([
- 'connection.layout',
- 'connection.move',
- 'connection.updateWaypoints',
- ], ifBpmn(updateConnectionWaypoints));
- this.reverted([
- 'connection.layout',
- 'connection.move',
- 'connection.updateWaypoints',
- ], ifBpmn(updateConnectionWaypoints));
- // update conditional/default flows
- this.executed('connection.reconnect', ifBpmn(function(event) {
- var context = event.context,
- connection = context.connection,
- oldSource = context.oldSource,
- newSource = context.newSource,
- connectionBo = getBusinessObject(connection),
- oldSourceBo = getBusinessObject(oldSource),
- newSourceBo = getBusinessObject(newSource);
- // remove condition from connection on reconnect to new source
- // if new source can NOT have condional sequence flow
- if (connectionBo.conditionExpression && !isAny(newSourceBo, [
- 'bpmn:Activity',
- 'bpmn:ExclusiveGateway',
- 'bpmn:InclusiveGateway'
- ])) {
- context.oldConditionExpression = connectionBo.conditionExpression;
- delete connectionBo.conditionExpression;
- }
- // remove default from old source flow on reconnect to new source
- // if source changed
- if (oldSource !== newSource && oldSourceBo.default === connectionBo) {
- context.oldDefault = oldSourceBo.default;
- delete oldSourceBo.default;
- }
- }));
- this.reverted('connection.reconnect', ifBpmn(function(event) {
- var context = event.context,
- connection = context.connection,
- oldSource = context.oldSource,
- newSource = context.newSource,
- connectionBo = getBusinessObject(connection),
- oldSourceBo = getBusinessObject(oldSource),
- newSourceBo = getBusinessObject(newSource);
- // add condition to connection on revert reconnect to new source
- if (context.oldConditionExpression) {
- connectionBo.conditionExpression = context.oldConditionExpression;
- }
- // add default to old source on revert reconnect to new source
- if (context.oldDefault) {
- oldSourceBo.default = context.oldDefault;
- delete newSourceBo.default;
- }
- }));
- // update attachments
- function updateAttachment(e) {
- self.updateAttachment(e.context);
- }
- this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
- this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
- }
- inherits(BpmnUpdater, CommandInterceptor);
- BpmnUpdater.$inject = [
- 'eventBus',
- 'bpmnFactory',
- 'connectionDocking',
- 'translate'
- ];
- // implementation //////////////////////
- BpmnUpdater.prototype.updateAttachment = function(context) {
- var shape = context.shape,
- businessObject = shape.businessObject,
- host = shape.host;
- businessObject.attachedToRef = host && host.businessObject;
- };
- BpmnUpdater.prototype.updateParent = function(element, oldParent) {
- // do not update BPMN 2.0 label parent
- if (element instanceof Label) {
- return;
- }
- // data stores in collaborations are handled separately by DataStoreBehavior
- if (is(element, 'bpmn:DataStoreReference') &&
- element.parent &&
- is(element.parent, 'bpmn:Collaboration')) {
- return;
- }
- var parentShape = element.parent;
- var businessObject = element.businessObject,
- parentBusinessObject = parentShape && parentShape.businessObject,
- parentDi = parentBusinessObject && parentBusinessObject.di;
- if (is(element, 'bpmn:FlowNode')) {
- this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
- }
- if (is(element, 'bpmn:DataOutputAssociation')) {
- if (element.source) {
- parentBusinessObject = element.source.businessObject;
- } else {
- parentBusinessObject = null;
- }
- }
- if (is(element, 'bpmn:DataInputAssociation')) {
- if (element.target) {
- parentBusinessObject = element.target.businessObject;
- } else {
- parentBusinessObject = null;
- }
- }
- this.updateSemanticParent(businessObject, parentBusinessObject);
- if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
- this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
- }
- this.updateDiParent(businessObject.di, parentDi);
- };
- BpmnUpdater.prototype.updateBounds = function(shape) {
- var di = shape.businessObject.di;
- var target = (shape instanceof Label) ? this._getLabel(di) : di;
- var bounds = target.bounds;
- if (!bounds) {
- bounds = this._bpmnFactory.createDiBounds();
- target.set('bounds', bounds);
- }
- assign(bounds, {
- x: shape.x,
- y: shape.y,
- width: shape.width,
- height: shape.height
- });
- };
- BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
- if (oldContainment === newContainment) {
- return;
- }
- var oldRefs, newRefs;
- if (is (oldContainment, 'bpmn:Lane')) {
- oldRefs = oldContainment.get('flowNodeRef');
- collectionRemove(oldRefs, businessObject);
- }
- if (is(newContainment, 'bpmn:Lane')) {
- newRefs = newContainment.get('flowNodeRef');
- collectionAdd(newRefs, businessObject);
- }
- };
- // update existing sourceElement and targetElement di information
- BpmnUpdater.prototype.updateDiConnection = function(di, newSource, newTarget) {
- if (di.sourceElement && di.sourceElement.bpmnElement !== newSource) {
- di.sourceElement = newSource && newSource.di;
- }
- if (di.targetElement && di.targetElement.bpmnElement !== newTarget) {
- di.targetElement = newTarget && newTarget.di;
- }
- };
- BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
- if (parentDi && !is(parentDi, 'bpmndi:BPMNPlane')) {
- parentDi = parentDi.$parent;
- }
- if (di.$parent === parentDi) {
- return;
- }
- var planeElements = (parentDi || di.$parent).get('planeElement');
- if (parentDi) {
- planeElements.push(di);
- di.$parent = parentDi;
- } else {
- collectionRemove(planeElements, di);
- di.$parent = null;
- }
- };
- function getDefinitions(element) {
- while (element && !is(element, 'bpmn:Definitions')) {
- element = element.$parent;
- }
- return element;
- }
- BpmnUpdater.prototype.getLaneSet = function(container) {
- var laneSet, laneSets;
- // bpmn:Lane
- if (is(container, 'bpmn:Lane')) {
- laneSet = container.childLaneSet;
- if (!laneSet) {
- laneSet = this._bpmnFactory.create('bpmn:LaneSet');
- container.childLaneSet = laneSet;
- laneSet.$parent = container;
- }
- return laneSet;
- }
- // bpmn:Participant
- if (is(container, 'bpmn:Participant')) {
- container = container.processRef;
- }
- // bpmn:FlowElementsContainer
- laneSets = container.get('laneSets');
- laneSet = laneSets[0];
- if (!laneSet) {
- laneSet = this._bpmnFactory.create('bpmn:LaneSet');
- laneSet.$parent = container;
- laneSets.push(laneSet);
- }
- return laneSet;
- };
- BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
- var containment,
- translate = this._translate;
- if (businessObject.$parent === newParent) {
- return;
- }
- if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) {
- if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
- newParent = newParent.processRef;
- }
- // already in correct ioSpecification
- if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
- return;
- }
- }
- if (is(businessObject, 'bpmn:Lane')) {
- if (newParent) {
- newParent = this.getLaneSet(newParent);
- }
- containment = 'lanes';
- } else
- if (is(businessObject, 'bpmn:FlowElement')) {
- if (newParent) {
- if (is(newParent, 'bpmn:Participant')) {
- newParent = newParent.processRef;
- } else
- if (is(newParent, 'bpmn:Lane')) {
- do {
- // unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
- newParent = newParent.$parent.$parent;
- } while (is(newParent, 'bpmn:Lane'));
- }
- }
- containment = 'flowElements';
- } else
- if (is(businessObject, 'bpmn:Artifact')) {
- while (newParent &&
- !is(newParent, 'bpmn:Process') &&
- !is(newParent, 'bpmn:SubProcess') &&
- !is(newParent, 'bpmn:Collaboration')) {
- if (is(newParent, 'bpmn:Participant')) {
- newParent = newParent.processRef;
- break;
- } else {
- newParent = newParent.$parent;
- }
- }
- containment = 'artifacts';
- } else
- if (is(businessObject, 'bpmn:MessageFlow')) {
- containment = 'messageFlows';
- } else
- if (is(businessObject, 'bpmn:Participant')) {
- containment = 'participants';
- // make sure the participants process is properly attached / detached
- // from the XML document
- var process = businessObject.processRef,
- definitions;
- if (process) {
- definitions = getDefinitions(businessObject.$parent || newParent);
- if (businessObject.$parent) {
- collectionRemove(definitions.get('rootElements'), process);
- process.$parent = null;
- }
- if (newParent) {
- collectionAdd(definitions.get('rootElements'), process);
- process.$parent = definitions;
- }
- }
- } else
- if (is(businessObject, 'bpmn:DataOutputAssociation')) {
- containment = 'dataOutputAssociations';
- } else
- if (is(businessObject, 'bpmn:DataInputAssociation')) {
- containment = 'dataInputAssociations';
- }
- if (!containment) {
- throw new Error(translate(
- 'no parent for {element} in {parent}',
- {
- element: businessObject.id,
- parent: newParent.id
- }
- ));
- }
- var children;
- if (businessObject.$parent) {
- // remove from old parent
- children = businessObject.$parent.get(containment);
- collectionRemove(children, businessObject);
- }
- if (!newParent) {
- businessObject.$parent = null;
- } else {
- // add to new parent
- children = newParent.get(containment);
- children.push(businessObject);
- businessObject.$parent = newParent;
- }
- if (visualParent) {
- var diChildren = visualParent.get(containment);
- collectionRemove(children, businessObject);
- if (newParent) {
- if (!diChildren) {
- diChildren = [];
- newParent.set(containment, diChildren);
- }
- diChildren.push(businessObject);
- }
- }
- };
- BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
- connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
- };
- BpmnUpdater.prototype.updateConnection = function(context) {
- var connection = context.connection,
- businessObject = getBusinessObject(connection),
- newSource = getBusinessObject(connection.source),
- newTarget = getBusinessObject(connection.target),
- visualParent;
- if (!is(businessObject, 'bpmn:DataAssociation')) {
- var inverseSet = is(businessObject, 'bpmn:SequenceFlow');
- if (businessObject.sourceRef !== newSource) {
- if (inverseSet) {
- collectionRemove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
- if (newSource && newSource.get('outgoing')) {
- newSource.get('outgoing').push(businessObject);
- }
- }
- businessObject.sourceRef = newSource;
- }
- if (businessObject.targetRef !== newTarget) {
- if (inverseSet) {
- collectionRemove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
- if (newTarget && newTarget.get('incoming')) {
- newTarget.get('incoming').push(businessObject);
- }
- }
- businessObject.targetRef = newTarget;
- }
- } else
- if (is(businessObject, 'bpmn:DataInputAssociation')) {
- // handle obnoxious isMsome sourceRef
- businessObject.get('sourceRef')[0] = newSource;
- visualParent = context.parent || context.newParent || newTarget;
- this.updateSemanticParent(businessObject, newTarget, visualParent);
- } else
- if (is(businessObject, 'bpmn:DataOutputAssociation')) {
- visualParent = context.parent || context.newParent || newSource;
- this.updateSemanticParent(businessObject, newSource, visualParent);
- // targetRef = new target
- businessObject.targetRef = newTarget;
- }
- this.updateConnectionWaypoints(connection);
- this.updateDiConnection(businessObject.di, newSource, newTarget);
- };
- // helpers //////////////////////
- BpmnUpdater.prototype._getLabel = function(di) {
- if (!di.label) {
- di.label = this._bpmnFactory.createDiLabel();
- }
- return di.label;
- };
- /**
- * Make sure the event listener is only called
- * if the touched element is a BPMN element.
- *
- * @param {Function} fn
- * @return {Function} guarded function
- */
- function ifBpmn(fn) {
- return function(event) {
- var context = event.context,
- element = context.shape || context.connection;
- if (is(element, 'bpmn:BaseElement')) {
- fn(event);
- }
- };
- }
|