bpmn学习笔记
学习笔记1
项目初始化, 对官方提供的一些demo进行学习, 整理, 微调
前期准备
本blog代码地址: https://github.com/chase-si/learn-bpmn
该装的npm包都装上, 核心的包就是
bpmn-js, 如果需要编辑流程参数等需求的话, 酌情根据官方demo引入别的包 官方demo: https://github.com/bpmn-io/bpmn-js-examples该引入的对应css都引入 我是在全局总css内, 用css的引用方法引入的, 参考如下
@import url("../node_modules/bpmn-js/dist/assets/diagram-js.css");
@import url("../node_modules/bpmn-js/dist/assets/bpmn-js.css");
@import url("../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css");
@import url("../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css");
@import url("../node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css");
/* 属性模板css */
@import url("../node_modules/bpmn-js-properties-panel/dist/assets/properties-panel.css");
基本显示
file: src/demos/basicBpmn
核心代码:
import BpmnJs from 'bpmn-js';
import { diagramXML } from './constants'; // xml的字符串
const bpmnViewer = new BpmnJs({
container: ${绑定的dom} // 比如 "#container" 或者 react/vue里ref绑定的dom指向
})
bpmnViewer.importXML(diagramXML);
添加import监听:
bpmnViewer.current.on('import.done', event => {
const { error, warnings } = event
if (error) {
return console.log('加载失败:', error)
}
return console.log('加载成功:', warnings);
})
带左边栏和可编辑
file: src/demos/modeler
核心代码:
import BpmnModeler from 'bpmn-js/lib/Modeler';
import { diagramXML } from './constants'; // xml的字符串
const = new BpmnModeler({
container: ${绑定的dom} // 比如 "#container" 或者 react/vue里ref绑定的dom指向
})
bpmnViewer.importXML(diagramXML);
基本同基本显示demo, 只不过BpmnJS换成了BpmnModeler
打印输出编辑后的xml的function:
// 把function绑定到click事件上触发即可, 注意`saveXML`是个promise异步
const prnNewXml = async () => {
const res = await bpmnViewer.saveXML()
console.log('new xml', res)
}
下载为svg, 和打印输出xml差不多
const prnNewSVG = async () => {
const res = await bpmnModeler.current.saveSVG()
const { svg } = res
const aLink = document.createElement('a')
aLink.href = `data:application/bpmn20-xml;charset=UTF-8,${encodeURIComponent(svg)}`
aLink.download = 'test.svg'
aLink.click()
}
带参数编辑的组件
file:src/demos/modelerPanel
核心代码, 加载之后每个小模块点中后可编辑其默认的属性, 有name,id, document之类的.
import BpmnModeler from 'bpmn-js/lib/Modeler';
import {
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
ZeebePropertiesProviderModule
} from 'bpmn-js-properties-panel';
import ZeebeBpmnModdle from 'zeebe-bpmn-moddle/resources/zeebe.json'
import { diagramXML } from './constants'; // xml的字符串
const bpmnViewer = new BpmnModeler({
container: ${绑定的xml渲染显示的dom},
propertiesPanel: {
parent: `${绑定的编辑面板显示的dom}`, // 这个dom的布局位置自己记得写css控制一下, 此处略
},
additionalModules: [
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule
]
})
bpmnViewer.importXML(diagramXML);
带拓展:
const bpmnViewer = new BpmnModeler({
container: ${绑定的xml渲染显示的dom},
propertiesPanel: {
parent: `${绑定的编辑面板显示的dom}`, // 这个dom的布局位置自己记得写css控制一下, 此处略
},
additionalModules: [
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule, // 只引入到此就可带基本的编辑功能, 默认都带id和document属性
ZeebePropertiesProviderModule // 此扩展增加了output和extension Properties
],
moddleExtensions: {
zeebe: ZeebeBpmnModdle
}
})
学习笔记2
改造, 覆写官方提供的demos甚至库代码, 实现可定制化的流程组件编辑 实现:
- 去掉logo
- 左侧边栏
palette自定义初始化组件 / 增加自定义组件 - 组件弹窗
contextPad自定义可连接初始化组件 / 增加自定义组件 contextPad的添加方向改为向下
file: src/demos/modelerCustomOverWrite
去掉logo
官方自带logo, 查看dom发现挂在一个a元素上, 我们在import xml完成后, 直接找到那个元素remove即可
bpmnModeler.on('import.done', (event) => {
if (event?.error) {
return console.error(event.error)
}
// 移除logo元素
const bpmnLogo = document.querySelector('a.bjs-powered-by')
if (bpmnLogo) {
bpmnLogo.remove()
}
})
自定义左边栏流程组件
官方提供了demo讲解如何自定义化组件:
https://github.com/bpmn-io/bpmn-js-example-custom-controls
引入实现了一下:
file: src/demos/modelerCustom
同时有一个bpmn中文学习文档里讲这个也比较透彻: bpmn LinDaiDai中文学习文档
但是, 这两个讲的都是新增自定义化组件, 但是我连初始化自带的组件都想删除, 研究了一番决定覆写他左侧边栏的加载代码.
覆盖原来的Modeler代码(how)
怎么做先写前面, 为什么这么做见下一个小标题
- 把
/node_modules/bpmn-js/lib/features/palette文件夹复制出来, 自定义个文件夹名字, 比如newPalette, 复制出的过程中, 记得palette文件内的相对路径引用要改成绝对路径的 - 覆盖替换原库中代码模块
import newPalette from './newPalette'
const newModelingModules = [...BpmnModeler.prototype._modelingModules]
newModelingModules.splice(19, 1, newPalette) // 替换原来的palette
const bpmnModeler = new BpmnModeler({
container: canvasDom.current,
modules: [
...Viewer.prototype._modules,
...BpmnModeler.prototype._interactionModules,
...newModelingModules
]
})
- 对newPalette里的一些代码做操作, 会发现左侧边栏会有变化, 在此我们可以多尝试调试帮助理解代码, 具体做哪些操作请看以下
阅读我们的Modeler代码(why)
点击我们的BpmnModeler直接跳进node_modules/bpmn-js/lib/Modeler.js, 发现其有三部分组成
Modeler.prototype._modules = [].concat(
Viewer.prototype._modules,
Modeler.prototype._interactionModules,
Modeler.prototype._modelingModules
);
而其中的第三部分_modelingModules中, paletteModule就是负责左边边栏渲染的
Modeler.prototype._modelingModules = [
// modeling components
AlignElementsModule,
AutoPlaceModule,
AutoScrollModule,
AutoResizeModule,
BendpointsModule,
ConnectModule,
ConnectionPreviewModule,
ContextPadModule,
CopyPasteModule,
CreateModule,
DistributeElementsModule,
EditorActionsModule,
GridSnappingModule,
InteractionEventsModule,
KeyboardModule,
KeyboardMoveSelectionModule,
LabelEditingModule,
ModelingModule,
MoveModule,
PaletteModule, // <============================ 这里
ReplacePreviewModule,
ResizeModule,
SnappingModule,
SearchModule
];
从paletteModule进到node_modules/bpmn-js/lib/features/palette/PaletteProvider.js, 看到有如下代码:
assign(actions, {
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: translate('Activate the hand tool'),
action: {
click: function(event) {
handTool.activateHand(event);
}
}
},
'lasso-tool': {
group: 'tools',
className: 'bpmn-icon-lasso-tool',
title: translate('Activate the lasso tool'),
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
...
'create.start-event': createAction(
'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
translate('Create StartEvent')
),
...
}
拿第一个工具图标来看
'hand-tool': {
group: 'tools',
className: 'bpmn-icon-hand-tool',
title: translate('Activate the hand tool'), // 这里是悬浮在icon上显示的文本, 需要汉化的直接改中文的就可
action: {
click: function(event) {
handTool.activateHand(event);
}
}
},
hand-tool显然就是左侧边栏的第一个小手图标, 对其各个属性进行猜测与测试验证:
className 代表图标的icon, bpmn是用style实现icon样式, 同时我发现node_modules/diagram-js/lib/features/palette/Palette.js中, 他有的对imageUrl属性的支持的, 所以如果传入图片路径给imageUrl, 可以实现icon替换
title就是hover时显示的文本, 需要汉化的直接这里改了拉倒
action代表触发事件, 此处未对他原来支持的action深究, 现阶段直接套用即可
拿后面的任务图标来看
'create.task': createAction(
'bpmn:Task', 'activity', 'bpmn-icon-task',
translate('Create Task')
),
这里就是我们大展手脚添加自定义组件的地方了, 只要对keycreate.task动动手脚, 我可以创出多个类似task图标的流程组件,
比如添加
'create.task2': createAction(
'bpmn:Task', 'activity', 'bpmn-icon-task',
translate('Create Task 2号')
),
会发现左边栏多了一个长得一样又不完全一致的task icon
目前代码中对组件type的定义比较全的可看/bpmn-js/lib/features/replace/ReplaceOptions.js
至于删除初始组件, 直接把初始列表里不要的注释掉就行.
写到这里, 接下来就可以把自定义组件的参数JSON化, 作为入参在初始化Modeler加载, 过程不表了. 最终代码可看file: src/demos/modelerCustomOverWrite
自定义menu菜单(点中xml渲染图中组件后, 显示的可接着添加的组件列表)
核心思想和替换PaletteModule类似
Modeler.prototype._modelingModules = [
// modeling components
AlignElementsModule,
AutoPlaceModule,
AutoScrollModule,
AutoResizeModule,
BendpointsModule,
ConnectModule,
ConnectionPreviewModule,
ContextPadModule, // <============================ 管事的在这里
CopyPasteModule,
CreateModule,
DistributeElementsModule,
EditorActionsModule,
GridSnappingModule,
InteractionEventsModule,
KeyboardModule,
KeyboardMoveSelectionModule,
LabelEditingModule,
ModelingModule,
MoveModule,
PaletteModule,
ReplacePreviewModule,
ResizeModule,
SnappingModule,
SearchModule
];
从node_modules/diagram-js/lib/features/centext-pad复制代码出来替换, 过程略了
menu(contextPad)的添加方向改为向下
默认的添加方向是在右侧, 源代码还考虑到了添加多个组件做布局适配, 不建议修改.
如果要修改, 相关代码在:
/node_modules/bpmn-js/lib/features/auto-place/BpmnAutoPlaceUtil.js
+ // var position = {
+ // x: sourceTrbl.right + horizontalDistance + element.width / 2,
+ // y: sourceMid.y + getVerticalDistance(orientation, minDistance)
+ // };
+ // chase change: add to bottom position
var position = {
- x: sourceTrbl.right + horizontalDistance + element.width / 2,
- y: sourceMid.y + getVerticalDistance(orientation, minDistance)
+ x: sourceMid.x + getVerticalDistance(orientation, minDistance),
+ y: sourceTrbl.bottom + horizontalDistance + element.height / 2,
};
这个文件的在原来的代码库里相关引用比较多, 没法像上面的PaletteModule和ContextPadModule两个模块一样拖出来重写, 所以用了patch-package直接覆盖修改了node_modules包里的几行代码.
patch-package使用简介
- 安装
yarn add patch-package
- 修改
bpmn-js为锁定版本, 重新安装一个固定版本
yarn remove bpmn-js
yarn add bpmn-js@10.3.0
- . 到node_modules里修改想要改的代码
- 改完之后yarn patch-package
${对应修改的代码在的包}, 此处如果是修改自动布局位置, 应该跑
yarn patch-package bpmn-js
- 跑完之后发现会生成一个
/patches文件夹, 里面记录了你修改的代码, 这样别人在安装你的项目时, 会自动读这个修改, 同步覆盖到他的node_modules下
学习笔记3
自定义组件属性栏的编辑
官方demo
带编辑栏的官方demo已经在学习笔记(1)里讲过了, 下面实现一下自己封装的编辑栏组件
整体设计
- 编辑流程属性设计, 斜波代表一个自定义流程组件
+---------------+ load(1) +------------------------------+ +---------------------+
| | | | | | |
| left-bar | | | xml-render | | property-edit |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| +---------+ | | | | | |
| | xiebo | |<---------+ | +----------+ | | |
| | | | click to add shape(2) | | xiebo| | click to edit props(3)| |
| | +--+------------------------------> | | |-----------+---------------------> | |
| | | | | | | | | |
| +---+-----+ | | | | | | |
| | | +----------+ | | |
| | | ↑ | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+------+--------+ +-------------+----------------+ +-----------+---------+
↑ |
| |
|save to xml(5) |
| |
| |
| |
| save(4) |
-+----------------------------------------------------+
- 加载数据
- 接受新palett格式的数据, 约定RcProperties内为可编辑参数 [{key: "label名称斜波", value: '斜波描述内容'}]
- 调用init内的函数load到left-bar
- 点击加载斜波task
- 通过监听
shape.added的方式, 监听加载斜波 - 监听事件内通过
model.updatePropertites将RcProperties更新到xml内
- click聚焦xml-render面板内的斜波Task
- 如果有如果有RcProperties, 就在property-edit内渲染显示可编辑的参数
- 设置当前聚焦的task.element为当前编辑element(改变焦点或聚焦其他element时触发(4)update使用)
- 当前聚焦的斜波Task blur判定
- 判断新click的element的ID === 当前element.id
- 编辑完的数据save
- 更新xml内的(3)聚焦的task.element的xml(再次通过
model.updatePropertites)
代码示例:file: src/demos/modelerPanelOverWrite
学习笔记4
自定义添加组合组件: 添加类似一个自定义条件组件, 自动连着俩subProcess框
学会调用自身扩展包
首先还是这个modeler
const bpmnModeler = new BpmnModeler({
container: `#${domID}`
modules:
})
通过bpmnModeler.injector._providers能获取所有能用的扩展包, 非常多, 当前版本(10.3.0)是157个, 其中有2个应该是我自己注入的.
所有这里能看到的扩展包都能用bpmnModeler.get(${扩展包名})获取调用
目前我了解的就几个, 其他无说明的, 等后面接触到了慢慢补充吧.
| 扩展名 | 说明 |
|---|---|
| "adaptiveLabelPositioningBehavior", | |
| "alignElements", | |
| "alignElementsContextPadProvider", | |
| "alignElementsMenuProvider", | |
| "appendBehavior", | |
| "associationBehavior", | |
| "attachEventBehavior", | |
| "attachSupport", | |
| "autoPlace", | contextPad的自动添加有用到, 下文会提及 |
| "autoPlaceSelectionBehavior", | |
| "autoScroll", | |
| "bendpointMove", | |
| "bendpointMovePreview", | |
| "bendpointSnapping", | |
| "bendpoints", | |
| "boundaryEventBehavior", | |
| "bpmnAlignElements", | |
| "bpmnAutoPlace", | |
| "bpmnAutoResize", | |
| "bpmnAutoResizeProvider", | |
| "bpmnCopyPaste", | |
| "bpmnDiOrdering", | |
| "bpmnDistributeElements", | |
| "bpmnFactory", | |
| "bpmnGridSnapping", | |
| "bpmnImporter", | |
| "bpmnInteractionEvents", | |
| "bpmnOrderingProvider", | |
| "bpmnRenderer", | |
| "bpmnReplace", | |
| "bpmnReplacePreview", | |
| "bpmnRules", | |
| "bpmnSearch", | 可根据xml的中的元素id模糊搜索 |
| "bpmnUpdater", | |
| "bpmnjs", | |
| "canvas", | |
| "changeSupport", | |
| "clipboard", | |
| "commandStack", | |
| "config", | |
| "connect", | |
| "connectPreview", | |
| "connectSnapping", | |
| "connectionDocking", | |
| "connectionPreview", | |
| "connectionSegmentMove", | |
| "contextPad", | |
| "contextPadProvider", | |
| "copyPaste", | |
| "create", | |
| "createBehavior", | |
| "createDataObjectBehavior", | |
| "createMoveSnapping", | |
| "createParticipantBehavior", | |
| "createPreview", | |
| "dataInputAssociationBehavior", | |
| "dataStoreBehavior", | |
| "defaultRenderer", | |
| "deleteLaneBehavior", | |
| "detachEventBehavior", | |
| "directEditing", | |
| "distributeElements", | |
| "distributeElementsMenuProvider", | |
| "dragging", | |
| "drilldownBreadcrumbs", | |
| "drilldownCentering", | |
| "drilldownOverlayBehavior", | |
| "dropOnFlowBehavior", | |
| "editorActions", | |
| "elementFactory", | 内含创建新的组件的方法, 下文会提及 |
| "elementRegistry", | 可用于找出所有渲染出来的组件, 下文会提及 |
| "eventBasedGatewayBehavior", | |
| "eventBus", | 他下面的._listeners列出了所有可以监听的事件 |
| "fixHoverBehavior", | |
| "globalConnect", | |
| "graphicsFactory", | |
| "gridSnapping", | |
| "gridSnappingAutoPlaceBehavior", | |
| "gridSnappingLayoutConnectionBehavior", | |
| "gridSnappingParticipantBehavior", | |
| "gridSnappingResizeBehavior", | |
| "gridSnappingSpaceToolBehavior", | |
| "groupBehavior", | |
| "handTool", | |
| "hoverFix", | |
| "importDockingFix", | |
| "interactionEvents", | |
| "isHorizontalFix", | |
| "keyboard", | |
| "keyboardBindings", | |
| "keyboardMove", | |
| "keyboardMoveSelection", | |
| "labelBehavior", | |
| "labelEditingPreview", | |
| "labelEditingProvider", | |
| "labelSupport", | |
| "lassoTool", | |
| "layoutConnectionBehavior", | |
| "layouter", | |
| "messageFlowBehavior", | |
| "moddle", | |
| "moddleCopy", | |
| "modeling", | |
| "modelingFeedback", | |
| "mouse", | |
| "move", | |
| "moveCanvas", | |
| "movePreview", | |
| "outline", | |
| "overlays", | |
| "padEntries", | blog2中传入contextPad自定义组件的参数, 这里可获取 |
| "palette", | |
| "paletteEntries", | blog2中传入palette自定义组件的参数, 这里可获取 |
| "paletteProvider", | 左侧的palette模块 |
| "pathMap", | |
| "popupMenu", | |
| "previewSupport", | |
| "removeElementBehavior", | |
| "removeEmbeddedLabelBoundsBehavior", | |
| "removeParticipantBehavior", | |
| "replace", | contextPad的替换功能有用到 |
| "replaceConnectionBehavior", | |
| "replaceElementBehaviour", | |
| "replaceMenuProvider", | |
| "resize", | |
| "resizeBehavior", | |
| "resizeHandles", | |
| "resizeLaneBehavior", | |
| "resizePreview", | |
| "resizeSnapping", | |
| "rootElementReferenceBehavior", | |
| "rootElementsBehavior", | |
| "rules", | |
| "searchPad", | |
| "selection", | |
| "selectionBehavior", | |
| "selectionVisuals", | |
| "snapping", | |
| "spaceTool", | |
| "spaceToolBehavior", | |
| "spaceToolPreview", | |
| "styles", | |
| "subProcessPlaneBehavior", | |
| "subProcessStartEventBehavior", | |
| "subprocessCompatibility", | |
| "textRenderer", | |
| "toggleCollapseConnectionBehaviour", | |
| "toggleElementCollapseBehaviour", | |
| "toolManager", | |
| "tooltips", | |
| "touchFix", | |
| "touchInteractionEvents", | |
| "translate", | 翻译模块 |
| "unclaimIdBehavior", | |
| "unsetDefaultFlowBehavior", | |
| "updateFlowNodeRefsBehavior", | |
| "zoomScroll" |
代码实现contextPad添加组件demo
首先拖出一个开始节点 这一步很关键, 因为我还不会代码原地生成开始节点...
获取我们要用到的扩展
// 定义bpmnModeler, 略
...
const elementFactory = bpmnModeler.get('elementFactory')
const elementRegistry = bpmnModeler.get('elementRegistry')
const autoPlace = bpmnModeler.get('autoPlace')
- 创建一个新的task流程组件, 找到我们1里的开始节点, 自动连接上
const newTaskShape = elementFactory.createShape({ type: 'bpmn:ServiceTask' })
const [rootShape, startShape] = elementRegistry.getAll()
autoPlace.append(startShape, newTaskShape)
以上阅读的相关代码可见/bpmn-js/lib/features/context-pad/ContextPadProvider.js
demo的代码例子file: /src/demos/modelerAutoAdd
自定义条件组件实现demo
在学习笔记(2)里讲了如何创建一个自定义组件, 这里我们基于bpmn:ParallelGateway这个基础组件, 创建一个自定义的条件组件
export const ConditionPalett = {
key: 'create.condition',
type: 'bpmn:ParallelGateway',
group: 'gateway',
iconClassName: 'bpmn-icon-gateway-parallel',
title: '条件组件',
show: true
}
之后把我们的自定义条件组件丢到左侧菜单栏, 监听shape.added的事件
...
// 自定义的条件组件, 自动带一个subProcess出现
if (target.type === 'bpmn:ParallelGateway') {
const elementFactory = modeler.get('elementFactory')
const elementRegistry = modeler.get('elementRegistry')
const autoPlace = modeler.get('autoPlace')
const newTaskShape = elementFactory.createShape({
type: 'bpmn:SubProcess',
isExpanded: true,
rcProperties: [{
key: 'name',
value: 'true',
}]
})
autoPlace.append(target, newTaskShape)
}
...
demo的代码例子file: /src/demos/modelerAutoAdd