preface
Since Activiti and BPMN. js were required for the project before, I learned and operated a lot. During the learning and using process, I often went to Baidu and found that articles on Activiti7 and BPMN. js were relatively few, or the description was not detailed enough and the version was relatively old. So let’s document some of the steps and actions I took to integrate BPMn.js.
I will record some Activiti7 operations in the back of Spring Boot integration next time. This time, I will take a look at how Vue integrates BPMN. js to realize online drawing, Xml export, SVG, online saving and other operations.
If you have any good suggestions or questions, you can send an email to [email protected].
Github complete code BPMN – Demo author blog Winily
Preparatory work
First of all needless to say there must be a Vue project first, first build a Vue project, install dependencies.
// Create a Vue project Vue create BPMN -demo // Install project dependencies, I prefer yarn, yarn // or use NPM NPM installCopy the code
With the project done, all we need to do is install the BPMN-JS dependencies and integrate the code. Start by installing a dependency
// yarn install yarn add bpmn-js // NPM install NPM install bpmn-jsCopy the code
First of all, open the project main.js and import the font library and style file about BPMN-JS. If you do not import these style files, the left control menu of BPMN-JS will not be displayed
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
Copy the code
Create the Bpmn modeler
Once the preparation is done, it is time to use BPMN-js in Vue. Here I will write the code directly in app.vue for convenience, and then write it into the component as required.
First of all, bPMN-JS modeler is actually implemented through Canvas, we create a div in the template for Bpmn to create the modeler.
<template>
<div id="app">
<div class="container">
<! Npmn-js implements drawing on canvas and sets ref to get element from vue.
<div class="bpmn-canvas" ref="canvas"></div>
</div>
</div>
</template>
Copy the code
With the Html part written, let’s implement the JS part. To create the modeler, we use the BpmnModeler object. We create the modeler primarily by creating this object.
// Introduce the Bpmn modeler object here
import BpmnModeler from "bpmn-js/lib/Modeler";
export default {
data() {
return {
bpmnModeler: null.canvas: null.// This part of the specific code I put below
initTemplate: ` slightly `
};
},
methods: {
init() {
// Get the canvas element
this.canvas = this.$refs.canvas;
// Create a Bpmn object
this.bpmnModeler = new BpmnModeler({
// Set BPMN's drawing container to the canvas element fetched at the door
container: this.canvas
});
// Initialize the modeler content
this.initDiagram(this.initTemplate);
},
initDiagram(bpmn) {
// Import XML into the BPMN-JS modeler
this.bpmnModeler.importXML(bpmn, err => {
if (err) {
this.$Message.error("Error opening model, please confirm that model conforms to Bpmn2.0 specification"); }}); }},// The bPMN-JS modeler is created by calling the init function after the component is loaded
mounted() {
this.init(); }};Copy the code
This part of THE XML is the Bpmn flowchart template code, this template contains a start node in the inside, according to your needs to create or modify the template, of course I do not recommend manual modification of the XML code. The way to create a template I recommend is to create a desired model in the BPMN-JS modeler, then export an XML or Bpmn file and copy the code from the file to use.
One of the things THAT I noticed in the Activiti7 back end of the XML template that I used was the definitions tag at the XML header where I introduced a lot of namespaces, If your flowchart needs to be deployed in Activiti, you must import the Activiti namespace. The following template is an Xml template for Activiti. Failure to introduce or conform to Activiti’s canonical namespace will result in model deployment failure. It is recommended that the model be modified based on the following template if it will eventually be deployed to Activiti.
<?xml version="1.0" encoding="UTF-8"? >
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:activiti="http://activiti.org/bpmn"
id="m1577635100724"
name=""
targetNamespace="http://www.activiti.org/testm1577635100724"
>
<process id="process" processType="None" isClosed="false" isExecutable="true">
<extensionElements>
<camunda:properties>
<camunda:property name="a" value="1" />
</camunda:properties>
</extensionElements>
<startEvent id="_2" name="start" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane id="BPMNPlane_leave" bpmnElement="leave">
<bpmndi:BPMNShape id="BPMNShape__2" bpmnElement="_2">
<omgdc:Bounds x="144" y="368" width="32" height="32" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="149" y="400" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
Copy the code
Now that we have created the modeler, let’s see what it looks like.
Add the bPMn-js-properties-panel plugin
Bpmn-js does not support the setting of Activities custom values, so it needs to introduce additional plug-ins for integration. Bpmn-js-properties-panel and Camunda-bPMn-Moddle should be installed first
Bpmn-js-properties-panel provides a property editor for the modeler, and Camunda-bPMn-Moddle extends what the property editor can edit. These attributes, such as Assignee of Activitie, rely on camunda-bPMn-moddle to provide editing.
Yarn add bpmn-js-properties-panel yarn add camunda-bpmn-moddle // NPM install NPM install bPMn-js-properties-panel npm install camunda-bpmn-moddleCopy the code
After installing the dependency, introduce the bPMn-js-properties-panel style in main.js otherwise the toolbar will not display
// Style the left toolbar and edit the node
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'
Copy the code
Create a div in app. vue to give the toolbar a location to display. I’ll just put it under the canvas
<! -- Where the toolbar displays -->
<div class="bpmn-js-properties-panel" id="js-properties-panel"></div>
Copy the code
Then import the toolbar and configure toolbar support
// Toolbar related
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";
import propertiesPanelModule from "bpmn-js-properties-panel";
import camundaModdleDescriptor from "camunda-bpmn-moddle/resources/camunda";
Copy the code
The modified init function
init() {
// Get the canvas element
this.canvas = this.$refs.canvas;
// Create a Bpmn object
this.bpmnModeler = new BpmnModeler({
// Set BPMN's drawing container to the canvas element fetched at the door
container: this.canvas,
// Add toolbar support
propertiesPanel: {
parent: "#js-properties-panel"
},
additionalModules: [propertiesProviderModule, propertiesPanelModule],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
this.createNewDiagram(this.bpmnTemplate);
}
Copy the code
The effect
Implement new, import, export operations
To do this, we write buttons and styles and attach them to click events. I’m using the Element component
One thing to note here is that if you are using the process editor to edit the process and the process needs to be deployed to Activiti to run, there is a problem. Using the camunda-bPMn-Moddle setting, For example, setting Assignee to ‘triple’ results in CAMunda: Assignee = ‘triple’ resulting in XML, which does not meet Activiti’s specification requirements, In Activiti, Activiti :assignee=” ga3 “is required, otherwise it will not be recognized normally, So one of my solutions to this problem is to simply replace Camunda with Activiti with the re when the XML text is retrieved from the saveXML function so that the process can run normally.
<div class="action">
<! To open a file, I use Element's file upload component. The hook gets the file before uploading and then reads the file contents.
<el-upload class="upload-demo" :before-upload="openBpmn">
<el-button icon="el-icon-folder-opened"></el-button>
</el-upload>
<el-button class="new" icon="el-icon-circle-plus" @click="newDiagram"></el-button>
<el-button icon="el-icon-download" @click="downloadBpmn"></el-button>
<el-button icon="el-icon-picture" @click="downloadSvg"></el-button>
</div>
Copy the code
And it looks something like this
Next, implement the operation
Import file operation ()
I used Element’s file upload component, used a before-upload hook to fetch the file object and then retrieved the file contents from the file object and fed them to the Bpmn modeler.
openBpmn(file) {
const reader = new FileReader();
// Read the text information in the File object in utF-8 format
reader.readAsText(file, "utf-8");
reader.onload = (a)= > {
// Import the text information into the Bpmn modeler after reading
this.createNewDiagram(reader.result);
};
return false;
}
Copy the code
New Diagram Operation
Creating a new image is as simple as reloading the original template
newDiagram() {
this.createNewDiagram(this.bpmnTemplate);
},
Copy the code
Exporting files
For the export operation, I set a hidden A link here, because what I do is to get the file content, set the file name and file format, and then generate the download link to the A label and trigger the click event of the A label to trigger the download.
<a hidden ref="downloadLink"></a>
Copy the code
Download related operations
download({ name = "diagram.bpmn", data }) {
// Here is the hidden link set earlier
const downloadLink = this.$refs.downloadLink;
// Convert the data to urIs and download the ones to use
const encodedData = encodeURIComponent(data);
if (data) {
// Pass the data to the link
downloadLink.href =
"data:application/bpmn20-xml; charset=UTF-8," + encodedData;
// Set the file name
downloadLink.download = name;
// Trigger the click event to start the downloaddownloadLink.click(); }},Copy the code
For convenience, I wrote a function to get the file name, so I’ll just take the model ID as the file name.
getFilename(xml) {
let start = xml.indexOf("process");
let filename = xml.substr(start, xml.indexOf(">"));
filename = filename.substr(filename.indexOf("id") + 4);
filename = filename.substr(0, filename.indexOf('"'));
return filename;
},
Copy the code
1. Export Bpmn operations
The operation of exporting Bpmn files is relatively simple. In fact, Bpmn files are no different from XML files except the suffix is not quite the same. In fact, Bpmn files are stored in THE XML tag structure. So we’re going to export and download the Bpmn file just by getting the XML of the model from the BPMN-JS modeler.
downloadBpmn() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if(! err) {// Get the file name
const name = `The ${this.getFilename(xml)}.bpmn`;
// Pass the file name and data to the download function
this.download({ name: name, data: xml }); }}); },Copy the code
2. Export SVG operations
Exporting SVG is a little bit more cumbersome, as I didn’t find any function in the BPMN-JS modeler that could provide the export, so I had to extract the SVG tags from the Canvas canvas and download them myself
downloadSvg() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if(! err) {// Get the file name
const name = `The ${this.getFilename(xml)}.svg`;
// Extract the SVG graphic label from the modeler canvas
let context = "";
const djsGroupAll = this.$refs.canvas.querySelectorAll(".djs-group");
for (let item of djsGroupAll) {
context += item.innerHTML;
}
// Get basic SVG data, length, width and height
const viewport = this.$refs.canvas
.querySelector(".viewport")
.getBBox();
// Concatenate labels and data into a fully normal SVG graph
const svg = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="${viewport.width}"
height="${viewport.height}"
viewBox="${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}"Version =" 1.1 ">${context}
</svg>
`;
// Pass the file name and data to the download function
this.download({ name: name, data: svg }); }}); },Copy the code
Final effect and complete code
Effect of screenshots
The complete code
- main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// BPMN dependencies
import 'bpmn-js/dist/assets/diagram-js.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css'
import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'
// Style the left toolbar and edit the node
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue.config.productionTip = false
new Vue({
router,
render: h= > h(App)
}).$mount('#app')
Copy the code
- App.vue
<template>
<div id="app">
<div class="container">
<! Npmn-js implements drawing on canvas and sets ref to get element from vue.
<div class="bpmn-canvas" ref="canvas"></div>
<! -- Where the toolbar displays -->
<div class="bpmn-js-properties-panel" id="js-properties-panel"></div>
<! Write the operation button inside this -->
<div class="action">
<el-upload action class="upload-demo" :before-upload="openBpmn">
<el-button icon="el-icon-folder-opened"></el-button>
</el-upload>
<el-button class="new" icon="el-icon-circle-plus" @click="newDiagram"></el-button>
<el-button icon="el-icon-download" @click="downloadBpmn"></el-button>
<el-button icon="el-icon-picture" @click="downloadSvg"></el-button>
<a hidden ref="downloadLink"></a>
</div>
</div>
</div>
</template>
<script>
import BpmnModeler from "bpmn-js/lib/Modeler";
// Toolbar related
import propertiesProviderModule from "bpmn-js-properties-panel/lib/provider/camunda";
import propertiesPanelModule from "bpmn-js-properties-panel";
import camundaModdleDescriptor from "camunda-bpmn-moddle/resources/camunda";
export default {
data() {
return {
bpmnModeler: null,
canvas: null, bpmnTemplate: ` <? xml version="1.0" encoding="UTF-8"? > <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:activiti="http://activiti.org/bpmn"
id="m1577635100724"
name=""
targetNamespace="http://www.activiti.org/testm1577635100724"
>
<process id="process" processType="None" isClosed="false" isExecutable="true">
<extensionElements>
<camunda:properties>
<camunda:property name="a" value="1" />
</camunda:properties>
</extensionElements>
<startEvent id="_2" name="start" />
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_leave">
<bpmndi:BPMNPlane id="BPMNPlane_leave" bpmnElement="leave">
<bpmndi:BPMNShape id="BPMNShape__2" bpmnElement="_2">
<omgdc:Bounds x="144" y="368" width="32" height="32" />
<bpmndi:BPMNLabel>
<omgdc:Bounds x="149" y="400" width="23" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
`
};
},
methods: {
newDiagram() {
this.createNewDiagram(this.bpmnTemplate);
},
downloadBpmn() {
this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if(! err) {// Get the file name
const name = `${this.getFilename(xml)}.bpmn`;
// Pass the file name and data to the download method
this.download({ name: name, data: xml }); }}); }, downloadSvg() {this.bpmnModeler.saveXML({ format: true }, (err, xml) => {
if(! err) {// Get the file name
const name = `${this.getFilename(xml)}.svg`;
// Extract the SVG graphic label from the modeler canvas
let context = "";
const djsGroupAll = this.$refs.canvas.querySelectorAll(".djs-group");
for (let item of djsGroupAll) {
context += item.innerHTML;
}
// Get basic SVG data, length, width and height
const viewport = this.$refs.canvas
.querySelector(".viewport")
.getBBox();
// Concatenate labels and data into a fully normal SVG graph
const svg = `
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="${viewport.width}"
height="${viewport.height}"
viewBox="${viewport.x} ${viewport.y} ${viewport.width} ${viewport.height}"
version="1.1"
>
${context}
</svg>
`;
// Pass the file name and data to the download method
this.download({ name: name, data: svg }); }}); }, openBpmn(file) {const reader = new FileReader();
// Read the text information in the File object in utF-8 format
reader.readAsText(file, "utf-8");
reader.onload = () => {
// Import the text information into the Bpmn modeler after reading
this.createNewDiagram(reader.result);
};
return false;
},
getFilename(xml) {
let start = xml.indexOf("process");
let filename = xml.substr(start, xml.indexOf(">"));
filename = filename.substr(filename.indexOf("id") + 4);
filename = filename.substr(0, filename.indexOf('"'));
return filename;
},
download({ name = "diagram.bpmn", data }) {
// Here is the hidden link set earlier
const downloadLink = this.$refs.downloadLink;
// Convert input to URIs and download the required ones
const encodedData = encodeURIComponent(data);
if (data) {
// Pass the data to the link
downloadLink.href =
"data:application/bpmn20-xml; charset=UTF-8," + encodedData;
// Set the file name
downloadLink.download = name;
// Trigger the click event to start the download
downloadLink.click();
}
},
async init() {
// Get the canvas element
this.canvas = this.$refs.canvas;
// Create a Bpmn object
this.bpmnModeler = new BpmnModeler({
// Set BPMN's drawing container to the canvas element fetched at the door
container: this.canvas,
// Add toolbar support
propertiesPanel: {
parent: "#js-properties-panel"
},
additionalModules: [propertiesProviderModule, propertiesPanelModule],
moddleExtensions: {
camunda: camundaModdleDescriptor
}
});
await this.createNewDiagram(this.bpmnTemplate);
},
async createNewDiagram(bpmn) {
// Convert string to graph;
this.bpmnModeler.importXML(bpmn, err => {
if (err) {
this.$Message.error("Error opening model, please confirm that model conforms to Bpmn2.0 specification"); }}); } }, mounted() {this.init(); }};</script>
<style>
.bpmn-canvas {
width: 100%;
height: 100vh;
}
.action {
position: fixed;
bottom: 10px;
left: 10px;
display: flex;
}
.upload-demo {
margin-right: 10px;
}
.bpmn-js-properties-panel {
position: absolute;
top: 0;
right: 0px;
width: 300px;
}
</style>
Copy the code