简介
bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成,是一个用js编写的BPMN 2.0渲染工具包,bpmn.js的核心方法有两个viewer和modeler。其中viewer用来将BPMN2.0图嵌入到浏览器中,modeler是用来处理数据,并绘制bpmn图的。
安装
css
npm install --save bpmn-js
使用
下面是一个bpmn.js使用的最简单的例子,首先需要new一个modeler,然后调用importXML方法,将需要展示的bpmn图形显示在浏览器中。
javascript
import BpmnViewer from "bpmn-js"
import testDiagram from "./demo.bpmn"
const bpmnModeler = new BpmnViewer({ container: "#canvas" })
this.bpmnModeler = bpmnModeler
bpmnModeler.importXML(testDiagram, function (err) {
if (!err) {
console.log("success!");
viewer.get("canvas").zoom("fit-viewport");
} else {
console.log("something went wrong:", err);
}
})
与图形元素的交互
在流程图被加载之后可以将交互事件listener添加到bpmn的元素上。在调用importXML的回调函数中给图像元素绑定事件方法。其中事件对象e里包含了当前元素的相关信息。
可以通过两种方式来添加
- 挂载到diagram events,监听element并绑定事件
bpmn图形元素的常用事件,例如点击、双击、修改等等
javascript
const eventBus = this.bpmnModeler.get("eventBus") // 需要使用eventBus
const eventTypes = [
'element.hover',
'element.out',
'element.click',
'element.dblclick',
'element.mousedown',
'element.mouseup'
] // 需要监听的事件集合
eventTypes.forEach(function (eventType) {
eventBus.on(eventType, function (e) {
console.log(e);
})
})
- 直接在dom上监听
javascript
// each model element a data-element-id attribute attached to it in HTML
// select the end event
var endEventNode = document.querySelector('[data-element-id=END_EVENT]');
endEventNode.addEventListener('click', function(e) {
alert('clicked the end event!');
});
挂载到生命周期的Events
events可以挂载到modeler的生命周期中,还可以和图表进行交互,使用modeler.on或者EventBus.on来注册扩展模块中的事件。
csharp
// 常用的事件
const events = ['shape.added', 'shape.move.end', 'shape.removed', 'connect.end', 'connect.move']
events.forEach((event) => {
this.bpmnModeler.on(event, e => {
console.log(event, e)
var elementRegistry = this.bpmnModeler.get('elementRegistry')
var shape = e.element ? elementRegistry.get(e.element.id) : e.shape
console.log(shape)
})
})
获取 diagram元素的bpmn属性
diagram元素通过businessObject属性存储对底层BPMN元素的引用。 businessObject是从BPMN 2.0 XML导入并在导出过程中序列化的实际元素。 使用businessObject来读取和写入BPMN的特定属性。
附上bpmn.json了解bpmn的types、properties和relationships。
读取 BPMN Properties
ini
var elementRegistry = bpmnJS.get('elementRegistry');
var sequenceFlowElement = elementRegistry.get('SequenceFlow_1'),
sequenceFlow = sequenceFlowElement.businessObject;
sequenceFlow.name; // 'YES'
设置BPMN Properties
ini
var moddle = bpmnJS.get('moddle');
// create a BPMN element that can be serialized to XML during export
var newCondition = moddle.create('bpmn:FormaleExpression', {
body: '${ value > 100 }'
});
// write property, no undo support
sequenceFlow.conditionExpression = newCondition;
var modeling = bpmnJS.get('modeling');
// 支持撤消/重做
modeling.updateProperties(sequenceFlowElement, {
conditionExpression: newCondition
});
自定义model-扩展xml,写入自己的key和value
自定义model,可以让bpmn的viewer/modeler实例读取、创建和设置特定区域的数据到bpmn的xml,也就是可以写数据到导出结果,例如想传一些自己的key和value值,就可以通过自定义model实现。
如下图所示,扩展的数据以'<qa:analysis />'标签的形式被写入bpmn的xml里,同时可以被bpmn解读,可以在businessObject中获取。
定义一个json
bpmn的扩展必须定义在一个json里,可以看这个例子
json
{
"name": "QualityAssurance",
"uri": "http://some-company/schema/bpmn/qa",
"prefix": "qa",
"xml": {
"tagAlias": "lowerCase"
},
"types": [
{
"name": "AnalyzedNode",
"extends": [
"bpmn:FlowNode"
],
"properties": [
{
"name": "suitable",
"isAttr": true,
"type": "Float"
}
]
},
{
"name": "AnalysisDetails",
"superClass": [ "Element" ],
"properties": [
{
"name": "lastChecked",
"isAttr": true,
"type": "String"
},
{
"name": "nextCheck",
"isAttr": true,
"type": "String"
},
{
"name": "comments",
"isMany": true,
"type": "Comment"
}
]
},
...
],
...
}
需要注意的是:
- 可以给已经存在的元素添加扩展通过
extends: [ "bpmn:FlowNode", "bpmn:SequenceFlow" ]
,这样就给所有的元素及连接箭头扩展了一个属性。 - 自定义类型例如'extensionElements'必须是Element的子类。
配置bpmn.js
将json传递给bpmn.js实例。将其传递给[bpmn-moddle]以进行model创建
ini
var qaPackage = require('path/to/qaPackage.json');
var BpmnJS = require('bpmn-js');
var viewer = new BpmnJS({
moddleExtensions: {
qa: qaPackage
}
});
访问model数据
您可以通过类型搜索元素"bpmn:extensionElements"列表,并从中提取所需的信息。
ini
function getExtension(element, type) {
if (!element.extensionElements) {
return null;
}
return element.extensionElements.filter(function(e) {
return e.$instanceOf(type);
})[0];
}
viewer.on('element.click', function(event) {
var element = event.element,
moddle = viewer.get('moddle'),
// the underlaying BPMN 2.0 element
businessObject = element.businessObject,
analysis,
score,
message;
analysis = getExtension(businessObject, 'qa:AnalysisDetails');
score = businessObject.suitable;
if (isNaN(score)) {
message = 'No suitability score yet, dblclick to assign one';
} else {
message = 'Diagram element has a suitability score of ' + score;
}
if (analysis) {
message += '\n Last analyzed at ' + analysis.lastChecked;
}
window.alert(message);
});
设置model,向xml中写入数据
ini
viewer.on('element.click', function(event) {
...
analysis = getExtension(businessObject, 'qa:AnalysisDetails');
var result = parseFloat(window.prompt('assign a new suitability score to ' + businessObject.id), 10);
if (isNaN(result)) {
return;
}
businessObject.suitable = result;
if (!analysis) {
analysis = moddle.create('qa:AnalysisDetails');
if (!businessObject.extensionElements) {
businessObject.extensionElements = moddle.create('bpmn:ExtensionElements');
}
businessObject.extensionElements.get('values').push(analysis);
}
analysis.lastChecked = new Date().toString();
});
给diagram元素添加备注
使用bpmn-js的overlays给bpmn添加注释或备注
overlays是可以根据元素位置来给元素添加必要的说明和备注,但是不会被保存到xml文件中去,且overlays的由自己定义的DOM元素组成,可以根据自己的需求添加样式。
- 通过bpmnViewer.get('overlays')和Overlays#add来添加overlays
css
var overlayHtml = $('<div>Mixed up the labels?</div>');
overlayHtml.click(function(e) {
alert('someone clicked me');
});
// attach the overlayHtml to a node
overlays.add('SCAN_OK', {
position: {
bottom: 0,
right: 0
},
html: overlayHtml
});
方法Overlays#add
接收两个重要参数:element或elementId 和 overlay描述符。overlays描述符必须包含要作为overlays层附加的"html"元素,以及一个"position",指示要在该元素上添加的位置。 使用top
,left
,bottom
,right
来控制
- 删除overlays
csharp
// remove by id
var overlayId = overlays.add(...);
overlays.remove(overlayId);
// remove by element and/or type
overlays.remove({ element: 'SCAN_OK' });
```
- 设置bpmn
csharp
var bpmnViewer = new BpmnViewer({
container: '#canvas',
width: '100%',
height: '100%'
});
bpmnViewer.importXML(diagramXML, function(err) {
if (err) {
return console.error('could not import BPMN 2.0 diagram', err);
}
// retrieve services and work with them
bpmnViewer.get('overlays').add(...);
bpmnViewer.get('overlays').remove(...);
});
扩展bpmn-js,实现自己的元素
Modeler构造函数提供additionalModules选项用来添加新的模块,方便对其进行扩展。
简单来说,模块是功能单元,它们挂接在bpmn流程图的生命周期中,并提供封装方法。
扩展的基本使用方法
扩展首先来说就是要创造出,属于自己形状或图形的元素,同时对元素做一些限制和绑定一些事件等等,bpmn提供了以下几个文件供使用者去扩展
- elementFactory 根据diagram-js 内部数据模型创建形状元素和connections
- paletteProvider 定义左侧的图形面板,决定了可以绘制的元素种类
- customRenderer 定义如何绘制图形,给底层提供自定义绘制方案,例如drawTriangle
- customRules 定义允许与自定义元素的交互规则
- customUpdater 在图像业务对象发生变化时,更新自定义元素
- contextPadProvider 一个自定义上下文按钮,这个上下文可以对当前元素进行操作,将自定义元素连接到BPMN元素。
elementFactory
如果没有特殊需要,此模块可以不写
ini
// 先加载bpmn-js原本的ElementFactory
var BpmnElementFactory = require('bpmn-js/lib/features/modeling/ElementFactory');
// 创建自定义ElementFactory
function CustomElementFactory(bpmnFactory, moddle) {
BpmnElementFactory.call(this, bpmnFactory, moddle);
var self = this;
// 创建一个diagram-js元素,根据给定的类型
this.create = function(elementType, attrs) {
var type = attrs.type;
// 可以自定义自己的规则,例如给自定义元素的businessObject加上type
if (/^custom:/.test(type)) {
if (!attrs.businessObject) {
attrs.businessObject = {
type: type,
};
if(attrs.id) {
assign(attrs.businessObject, {
id: attrs.id
});
}
}
return self.createBpmnElement(elementType, attrs);
};
}
// CustomElementFactory继承ElementFactory
inherits(CustomElementFactory, BpmnElementFactory);
module.exports = CustomElementFactory;
paletteProvider
这个是给底层提供需要渲染到左侧面板的元素,左侧面板看下图,要进行扩展和选择性渲染,可以参考下面代码,基本写法就是这样
ini
// 创建一个PaletteProvider对象
function PaletteProvider(palette, create, elementFactory, spaceTool, lassoTool) {
this._create = create;
this._elementFactory = elementFactory;
this._spaceTool = spaceTool;
this._lassoTool = lassoTool;
palette.registerProvider(this);
}
module.exports = PaletteProvider;
PaletteProvider.$inject = [ 'palette', 'create', 'elementFactory', 'spaceTool', 'lassoTool' ];
// 添加getPaletteEntries方法
PaletteProvider.prototype.getPaletteEntries = function(element) {
var actions = {},
create = this._create,
elementFactory = this._elementFactory,
spaceTool = this._spaceTool,
lassoTool = this._lassoTool;
function createAction(type, group, className, title, options) {
function createListener(event) {
var shape = elementFactory.createShape(assign({ type: type }, options));
if (options) {
shape.businessObject.di.isExpanded = options.isExpanded;
}
create.start(event, shape);
}
var shortType = type.replace(/^bpmn:/, '');
return {
group: group,
className: className,
title: title || 'Create ' + shortType,
action: {
dragstart: createListener,
click: createListener
}
};
}
assign(actions, {
'custom-triangle': createAction(
'custom:triangle', 'custom', 'icon-custom-triangle'
),
'custom-circle': createAction(
'custom:circle', 'custom', 'icon-custom-circle'
),
...
'create.task': createAction(
'bpmn:Task', 'activity', 'bpmn-icon-task'
),
});
// 最终返回一个对象,里面包含所需要渲染的元素,同时绑定元素拖拽点击的相关事件
return actions;
}
customRenderer
继承原本的BaseRenderer,然后扩展一些draw方法
ini
var BaseRenderer = require('diagram-js/lib/draw/BaseRenderer');
function CustomRenderer(eventBus, styles) {
this.drawTriangle = function(p, size) {
}
}
inherits(CustomRenderer, BaseRenderer);
module.exports = CustomRenderer;
customRules
继承原本的RuleProvider,然后扩展一些rules,没有也可以不扩展,直接使用原本的。
javascript
var RuleProvider = require('diagram-js/lib/features/rules/RuleProvider');
function CustomRules(eventBus) {
RuleProvider.call(this, eventBus);
}
inherits(CustomRules, RuleProvider);
CustomRules.prototype.init = function() {
function canCreate(shape, target) {}
function canConnect(source, target) {}
this.addRule('elements.move', HIGH_PRIORITY, function(context) {}
...
}
customUpdater
负责更新自定义业务元素的处理程序。
scss
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
function CustomUpdater(eventBus, bpmnjs) {}
inherits(CustomUpdater, CommandInterceptor);
contextPadProvider
这个是给底层提供需要渲染到上下文面板的元素,上下文面板面板看下图,要进行扩展和选择性渲染,可以参考下面代码,基本写法就是这样
php
var ContextPadProvider = require('bpmn-js/lib/features/context-pad/ContextPadProvider');
function CustomContextPadProvider(eventBus, contextPad, modeling, elementFactory, connect,
create, popupMenu, canvas, rules, translate) {
ContextPadProvider.call(this, eventBus, contextPad, modeling, elementFactory, connect, create,popupMenu, canvas, rules, translate);
var cached = bind(this.getContextPadEntries, this);
this.getContextPadEntries = function(element) {
var actions = cached(element);
var businessObject = element.businessObject;
function startConnect(event, element, autoActivate) {
connect.start(event, element, autoActivate);
}
if (isAny(businessObject, [ 'custom:triangle', 'custom:circle'])) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
tittle: translate('Connect using custom connection'),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
return actions;
};
}
inherits(CustomContextPadProvider, ContextPadProvider);
将写好的几个文件组合添加到bpmn
javascript
const obj = {
__init__: [ 'customRenderer', 'paletteProvider', 'customRules', 'customUpdater', 'contextPadProvider' ],
elementFactory: [ 'type', require('./CustomElementFactory') ],
customRenderer: [ 'type', require('./CustomRenderer') ],
paletteProvider: [ 'type', require('./CustomPalette') ],
customRules: [ 'type', require('./CustomRules') ],
customUpdater: [ 'type', require('./CustomUpdater') ],
contextPadProvider: [ 'type', require('./CustomContextPadProvider') ]
};
// 在调用的时候将obj传给additionalModules
BpmnModeler = require('bpmn-js/lib/Modeler'),
var modeler = new BpmnModeler({
container: canvas,
additionalModules: obj,
});
国际化
首先是做一个映射,中英文对应
java
module.exports = {
// Labels
'Activate the global connect tool' : '激活全局连接工具',
'Append {type}': '添加{类型}',
....
}
写一个翻译方法,然后将映射引入该方法中
ini
var translations = require('./translationsGerman');
module.exports = function customTranslate(template, replacements) {
replacements = replacements || {};
// Translate
template = translations[template] || template;
// Replace
return template.replace(/{([^}]+)}/g, function(_, key) {
return replacements[key] || '{' + key + '}';
});
};
将翻译方法引入bpmn
ini
var BpmnModeler = require('bpmn-js/lib/Modeler');
// 定制翻译模块,将定制的翻译模块赋值给'value'
var customTranslate = {
translate: [ 'value', require('./customTranslate/customTranslate') ]
};
// 读取xml
var newDiagramXML = fs.readFileSync(__dirname + '/../resources/newDiagram.bpmn', 'utf-8');
// 获取Dom元素
var container = document.getElementById('canvas');
//引入翻译模块
var modeler = new BpmnModeler({
container: container,
additionalModules: [
customTranslate
]
});