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
  ]
});
相关推荐
万叶学编程2 小时前
Day02-JavaScript-Vue
前端·javascript·vue.js
前端李易安4 小时前
Web常见的攻击方式及防御方法
前端
PythonFun4 小时前
Python技巧:如何避免数据输入类型错误
前端·python
知否技术4 小时前
为什么nodejs成为后端开发者的新宠?
前端·后端·node.js
hakesashou4 小时前
python交互式命令时如何清除
java·前端·python
天涯学馆4 小时前
Next.js与NextAuth:身份验证实践
前端·javascript·next.js
HEX9CF5 小时前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
ConardLi5 小时前
Chrome:新的滚动捕捉事件助你实现更丝滑的动画效果!
前端·javascript·浏览器
ConardLi5 小时前
安全赋值运算符,新的 JavaScript 提案让你告别 trycatch !
前端·javascript