spiff-arena/bpmn-js-spiffworkflow/app/spiffworkflow/DataObject/DataObjectInterceptor.js

136 lines
5.8 KiB
JavaScript

import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getDi, is } from 'bpmn-js/lib/util/ModelUtil';
import { remove as collectionRemove } from 'diagram-js/lib/util/Collections';
import {
findDataObjects,
findDataObjectReferences,
idToHumanReadableName,
} from './DataObjectHelpers';
const HIGH_PRIORITY = 1500;
/**
* This Command Interceptor functions like the BpmnUpdator in BPMN.js - It hooks into events
* from Diagram.js and updates the underlying BPMN model accordingly.
*
* This handles some special cases we want to handle for DataObjects and DataObjectReferences,
* for instance:
* 1) Use existing data objects if possible when creating a new reference (don't create new objects each time)
* 2) Don't automatically delete a data object when you delete the reference - unless all references are removed.
* 3) Update the name of the DataObjectReference to match the id of the DataObject.
* 4) Don't allow someone to move a DataObjectReference from one process to another process.
*/
export default class DataObjectInterceptor extends CommandInterceptor {
constructor(eventBus, bpmnFactory, commandStack, bpmnUpdater) {
super(eventBus);
/* The default behavior is to move the data object into whatever object the reference is being created in.
* If a data object already has a parent, don't change it.
*/
bpmnUpdater.updateSemanticParent = (businessObject, parentBusinessObject) => {
// Special case for participant - which is a valid place to drop a data object, but it needs to be added
// to the particpant's Process (which isn't directly accessible in BPMN.io
let realParent = parentBusinessObject;
if (is(realParent, 'bpmn:Participant')) {
realParent = realParent.processRef;
}
if (is(businessObject, 'bpmn:DataObjectReference')) {
// For data object references, always update the flowElements when a parent is provided
// The parent could be null if it's being deleted, and I could probably handle that here instead of
// when the shape is deleted, but not interested in refactoring at the moment.
if (realParent != null) {
const flowElements = realParent.get('flowElements');
flowElements.push(businessObject);
}
} else if (is(businessObject, 'bpmn:DataObject')) {
// For data objects, only update the flowElements for new data objects, and set the parent so it doesn't get moved.
if (typeof (businessObject.$parent) === 'undefined') {
const flowElements = realParent.get('flowElements');
flowElements.push(businessObject);
businessObject.$parent = realParent;
}
} else {
bpmnUpdater.__proto__.updateSemanticParent.call(bpmnUpdater, businessObject, parentBusinessObject);
}
};
/**
* For DataObjectReferences only ...
* Prevent this from calling the CreateDataObjectBehavior in BPMN-js, as it will
* attempt to crete a dataObject immediately. We can't create the dataObject until
* we know where it is placed - as we want to reuse data objects of the parent when
* possible */
this.preExecute(['shape.create'], HIGH_PRIORITY, function (event) {
const { context } = event;
const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
event.stopPropagation();
}
});
/**
* Don't just create a new data object, use the first existing one if it already exists
*/
this.executed(['shape.create'], HIGH_PRIORITY, function (event) {
const { context } = event;
const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
const process = shape.parent.businessObject;
const existingDataObjects = findDataObjects(process);
let dataObject;
if (existingDataObjects.length > 0) {
dataObject = existingDataObjects[0];
} else {
dataObject = bpmnFactory.create('bpmn:DataObject');
}
// set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject;
shape.businessObject.$parent = process;
}
});
/**
* In order for the label to display correctly, we need to update it in POST step.
*/
this.postExecuted(['shape.create'], HIGH_PRIORITY, function (event) {
const { context } = event;
const { shape } = context;
// set the reference to the DataObject
// Update the name of the reference to match the data object's id.
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
commandStack.execute('element.updateProperties', {
element: shape,
moddleElement: shape.businessObject,
properties: {
name: idToHumanReadableName(shape.businessObject.dataObjectRef.id),
},
});
}
});
/**
* Don't remove the associated DataObject, unless all references to that data object
* Difficult to do given placement of this logic in the BPMN Updater, so we have
* to manually handle the removal.
*/
this.executed(['shape.delete'], HIGH_PRIORITY, function (event) {
const { context } = event;
const { shape } = context;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
const dataObject = shape.businessObject.dataObjectRef;
let flowElements = shape.businessObject.$parent.get('flowElements');
collectionRemove(flowElements, shape.businessObject);
let references = findDataObjectReferences(flowElements, dataObject.id);
if (references.length === 0) {
let flowElements = dataObject.$parent.get('flowElements');
collectionRemove(flowElements, dataObject);
}
}
});
}
}
DataObjectInterceptor.$inject = ['eventBus', 'bpmnFactory', 'commandStack', 'bpmnUpdater'];