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