BPMN import
Business Process Model and Notation (BPMN) is a graphical representation for specifying business processes in a business process model. Along with the BPMN specification, an XML schema is also defined that allows a BPMN diagram to be imported from XML.
JointJS+ provides a BPMN import plugin that enables the import process within the JointJS+ framework.
Installation
The BPMN import plugin is an independent module distributed as part of the JointJS+ archive:
- Unzip the archive and navigate to the
build/package-bpmn-import
folder inside. - Copy the
joint-bpmn-import.tgz
file and paste it into the root of your package.
Then, install the BPMN import package (@joint/format-bpmn-import
) using your package manager:
- npm
- pnpm
- yarn
npm add joint-bpmn-import.tgz
pnpm add ./joint-bpmn-import.tgz
yarn add @joint/format-bpmn-import@file:joint-bpmn-import.tgz
Now, you can import functionality from @joint/format-bpmn-import
into your application as necessary:
import { fromBPMN } from '@joint/format-bpmn-import';
const importResult = fromBPMN(xmlDoc);
There is also a UMD version available
Place the UMD distribution of the plugin into the root of your package:
- Navigate to the
build/package-bpmn-import/dist
folder inside the unzipped JointJS+ archive. - Copy the
joint.format.bpmnImport.js
file and paste it into the root of your package.
Include joint.format.bpmnImport.js
in your HTML:
- JointJS
- JointJS+
<script src="joint.js"></script>
<script src="joint.format.bpmnImport.js"></script>
Access fromBPMN
through the joint.format
namespace:
const importResult = joint.format.fromBPMN(xmlDoc);
Architecture and API explained
There are only a few core concepts to understand when working with this package.
fromBPMN(xmlDoc, options)
This function handles the import process. It takes an XMLDocument
containing a diagram and an options object. The
inclusion of the options parameter provides the flexibility to customize the import process to meet specific requirements.
Return value
The function returns a result object with the following properties:
cells: dia.Cell[]
An array of JointJS cells representing the imported BPMN diagram.
errors: string[]
An array of errors that occurred during the import process.
Import options object
It is an object with the following properties:
cellFactories
A collection of cell factories mapped by decisive XML elements.
Each cell factory is a function that gets an XML representation of a particular BPMN element as a parameter, and is
expected to create and return a corresponding JointJS cell, or null
, if the processed element should be completely
excluded from the import.
bpmn2Shapes
A namespace that contains definitions of the JointJS BPMN 2 shapes.
The following classes are expected to be defined in the given namespace: HeaderedPool
, Activity
, Event
, Gateway
,
Flow
, DataObject
, DataAssociation
, Annotation
, AnnotationLink
Decisive XML elements
The input XML document is made up of many XML elements in a hierarchy, and the first task that the import engine performs is to look for so-called decisive elements. Decisive elements are the main elements that represent BPMN shapes. The following is a list of such elements that are recognized by the import engine:
participant
task
serviceTask
sendTask
receiveTask
userTask
manualTask
businessRuleTask
scriptTask
subProcess
callActivity
startEvent
intermediateThrowEvent
intermediateCatchEvent
boundaryEvent
endEvent
gateway
parallelGateway
inclusiveGateway
complexGateway
eventBasedGateway
exclusiveGateway
sequenceFlow
messageFlow
dataObject
textAnnotation
association
Cell factories
Once some decisive element is identified, the next step is to convert it into a corresponding JointJS cell. The piece of the puzzle responsible for this conversion is called a cell factory.
It is a function that takes an xmlNode
as input and returns a joint.dia.Cell
instance representing that element in
the JointJS world:
(xmlNode: Element, xmlDoc: XMLDocument, defaultFactory: DefaultFactory) => joint.dia.Cell | null
But not only does it take an xmlNode
at the input. It takes the whole XMLDocument
as a second argument. The reason
for this is that the information needed to construct an element may be spread over several places in the XML document,
and the function needs to be able to look up all the pieces.
If the function returns null
, then the processed element is completely excluded from the import.
There is a set of default cell factories predefined in the package, converting decisive elements to the shapes from the
namespace specified in the bpmn2Shapes
property within the import options object. These factories can be accessed
through the defaultFactory
argument in custom cell factories, as demonstrated in the examples below.
An important thing to know is that the factory should only take care of the cell itself. Things like embedding it in a hierarchy (as in the case of some subprocesses) or linking it to other cells, these are all tasks that will be done by the import engine once the cell is returned from the factory.
Examples
Basic usage
The most basic way to use the package is to convert an XMLDocument
into JointJS cells by passing the document to the
fromBPMN
without specifying value of the cellFactories
property in the options
object.
The resulting cells are stored in the cells
property of the import result object.
const importResult = fromBPMN(XMLDocument, {
cellNamespace: joint.shapes.bpmn2
});
// graph.addCells(importResult.cells)
Usage with custom cell factories and built-in shapes
Providing a custom cell factory is a way how to customize the JointJS cell creation process. For example, it may happen that the cell created by a default factory is fine, but it needs to be refined a bit before being used in the diagram.
Let's take a look at the example where the goal is to have all common tasks with a light pink background. There is no way how to express the fill color in BPMN XML, but it can be defined on the JointJS cell. The way to achieve this is to define a custom cell factory that will let the default factory create a cell base and then just adjust the fill color as desired.
const importResult = fromBPMN(xmlDoc, {
cellFactories: {
'task': (xmlNode, xmlDoc, defaultFactory) => {
const cell = defaultFactory();
cell.attr('background/fill', '#FFDDDD')
return cell;
}
},
cellNamespace: joint.shapes.bpmn2
});
// graph.addCells(importResult.cells);
Note that using the default cell factory is optional. The cell can be created from scratch in a custom factory,
directly using the provided xmlNode
and possibly xmlDoc
, including the entire XML.
Usage with custom cell factories and custom shapes
Sometimes the shapes available in joint.shapes.bpmn2
may not cover all the needs of a project. In such case, it is
necessary to define custom shapes. To engage these custom shapes in the import process, it is necessary to define a
custom cell factory that returns a custom shape instead of one from the joint.shapes.bpmn2
package.
Note that, as in the previous example, a default cell factory can be used, but again it is optional and the cell of
the custom type can be created from scratch using xmlNode
and possibly a xmlDoc
directly.
const CustomShape = shapes.bpmn2.Activity.define(
'CustomShape'
);
const importResult = fromBPMN(xmlDoc, {
cellFactories: {
'task': (xmlNode, xmlDoc, defaultFactory) => {
const cell = defaultFactory();
return new CustomShape({
id: cell.id,
position: cell.position(),
size: cell.size(),
attrs: cell.attr()
});
}
},
cellNamespace: joint.shapes.bpmn2
});
// graph.addCells(importResult.cells);
Usage with a custom cell factory that works with a xmlDoc parameter
When implementing a custom cell factory, it may happen that some information, which is outside of the provided
xmlNode
, is needed. To make it accessible, a second parameter called xmlDoc
is provided. It contains the entire XML
document that is currently being imported.
In the following example, the default factory is not used. So all the cell related information has to be collected manually. Normally this would not be possible without the access to the entire XML document.
const importResult = fromBPMN(xmlDoc, {
cellFactories: {
'task': (xmlNode, xmlDoc) => {
const elementId = xmlNode.getAttribute('id');
const shapeXMLElement = xmlDoc.querySelector(`BPMNDiagram BPMNShape[bpmnElement=${elementId}]`);
if (shapeXMLElement === null) {
return null;
}
const bounds = shapeXMLElement.querySelector('Bounds');
if (bounds === null) {
return null;
}
const x = bounds.getAttribute('x');
const y = bounds.getAttribute('y');
const width = bounds.getAttribute('width');
const height = bounds.getAttribute('height');
if (x === null || y === null || width === null || height === null) {
return null;
}
const name = xmlNode.getAttribute('name') || '';
return new Activity({
id: elementId,
position: {
x: parseFloat(x),
y: parseFloat(y),
},
size: {
width: parseFloat(width),
height: parseFloat(height),
},
attrs: {
label: {
text: name
}
}
});
}
},
cellNamespace: joint.shapes.bpmn2
});
// graph.addCells(importResult.cells);
Coverage of the BPMN 2 domain
The BPMN 2 domain is large and the import plugin doesn't cover it all yet.
List of supported elements
- Tasks
- Task
- Service Task
- Send Task
- Receive Task
- User Task
- Manual Task
- Business Rule Task
- Script Task
- Subprocesses
- Events
- Start Event
- Intermediate Throw Event
- Intermediate Catch Event
- Boundary Event
- End Event
- Gateways
- Gateway
- Parallel Gateway
- Inclusive Gateway
- Complex Gateway
- Event Based Gateway
- Exclusive Gateway
- Sequence Flows
- Participants
- Pool
- Swimlane
- Message Flows
- Data Objects
- Data Object
- Data Input Association
- Data Input Association
- Data Output Association
- Text Annotations
- Associations
List of elements to be supported in the future
- Conversations
- Conversation Links
- Choreography