bpmn.js基础用法

简介

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",指示要在该元素上添加的位置。 使用topleftbottomright来控制

  • 删除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
  ]
});
相关推荐
Momo__1 小时前
VueUse createReusableTemplate —— 单文件组件内的模板复用神器
前端·vue.js
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
小小小小宇1 小时前
程序员如何给 LLM 装工具以及看懂推理过程
前端
写代码的皮筏艇1 小时前
React中的forwardRef
前端·react.js·面试
槑有老呆1 小时前
花三个月工资请了个 AI 程序员,结果它连青岛啤酒股价都查不了
前端
风骏时光牛马1 小时前
Verilog开发常见问题汇总解析
前端
子兮曰1 小时前
AI Coding Method Map:一张图看懂 AI 编程的完整链路
前端·人工智能·后端
weedsfly1 小时前
语法糖褪去之后——Babel 转译产物中的 JavaScript 本貌
前端·javascript
JustHappy1 小时前
「软件设计思想杂谈🤔」“切图仔”也能懂编译原理?框架源码也许没那么难。聊聊 Vue 的编译(上)
前端·javascript·vue.js