Q: bpmn.js是什么? 🤔️javascript
bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.css
Q: 我为何要写该系列的教材? 🤔️html
由于公司业务的须要于是要在项目中使用到bpmn.js
,可是因为bpmn.js
的开发者是国外友人, 所以国内对这方面的教材不多, 也没有详细的文档. 因此不少使用方式不少坑都得本身去找.在将其琢磨完以后, 决定写一系列关于它的教材来帮助更多bpmn.js
的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是本身对其的一种巩固.前端
因为是系列的文章, 因此更新的可能会比较频繁, 您要是无心间刷到了且不是您所须要的还请谅解😊.vue
不求赞👍不求心❤️. 只但愿能对你有一点小小的帮助.java
接着上一章节, 咱们已经知道了该如何自定义左侧的工具栏(Palette), 不了解的小伙伴能够移步: 《全网最详bpmn.js教材-自定义palette篇》.git
可是同时咱们也知道仅仅只改变Palette
是不够的, 由于绘画出来的图形仍是“裸体的”:github
这一章节咱们就来看一下如何自定义画布上的图形, 也就是实现自定义Renderer
的功能.web
经过阅读你能够学习到:数组
和自定义Palette
同样, 先来看看最简单的在原有的元素上进行修改.
让咱们接着在LinDaiDai/bpmn-vue-custom案例项目上进行开发.
在components
文件夹下新建一个custom-renderer.vue
文件, 同时配置路由“自定义renderer”.
在components/custom
文件夹下新建一个CustomRenderer.vue
文件, 用来自定义renderer
.
在components
文件夹下新建一个utils
文件夹同时新建util.js
文件, 用来放一些公共的方法和配置.
CustomRenderer.vue
代码因为是在bpmn.js
已有的元素上进行修改, 因此首先咱们能够先将BaseRenderer
这个类引入进来, 而后让咱们的自定义renderer
继承它:
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' // 引入默认的renderer
const HIGH_PRIORITY = 1500 // 最高优先级
export default class CustomRenderer extends BaseRenderer { // 继承BaseRenderer
constructor(eventBus, bpmnRenderer) {
super(eventBus, HIGH_PRIORITY)
this.bpmnRenderer = bpmnRenderer
}
canRender(element) {
// ignore labels
return !element.labelTarget
}
drawShape(parentNode, element) { // 核心函数就是绘制shape
const shape = this.bpmnRenderer.drawShape(parentNode, element)
return shape
}
getShapePath(shape) {
return this.bpmnRenderer.getShapePath(shape)
}
}
CustomRenderer.$inject = ['eventBus', 'bpmnRenderer']
复制代码
上面👆的代码很简单, 相信你们均可以看的明白.
注: 这里有个小坑要注意一下, 就是HIGH_PRIORITY
不可以去掉, 否则的话你会发现它不会执行下面的drawShpe
函数
到了这里可能就有小伙伴要问了, 感受你作了这么多并无什么用啊, 仍是没有看到关于自定义renderer
的效果呀😅!
没错, 只完成上面的步骤那是不够的, 关键是在于如何编写drawShape
这个方法.
drawShape
代码咱们能够先在前面建立好的utils/util.js
文件下写下此代码:
// util.js
const customElements = ['bpmn:Task']
export { customElements }
复制代码
也就是建立了一个名为customElements
的数组而后导出, 至于数组里为何只有一项bpmn:Task
?🤔️
那是由于在上一个案例中我建立的lindaidai-task
的类型就是bpmn:Task
类型的.
因此这个数组的做用就是用来放哪些类型是须要咱们自定义的, 从而在渲染的时候就能够与不须要自定义的元素做区分.
甚至你还能够作一些配置:
const customElements = ['bpmn:Task'] // 自定义元素的类型
const customConfig = { // 自定义元素的配置(后面会用到)
'bpmn:Task': {
'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png',
'attr': { x: 0, y: 0, width: 48, height: 48 }
}
}
export { customElements, customConfig }
复制代码
让咱们在CustomRenderer.js
中使用并编写它:
import { customElements, customConfig } from '../utils/util'
...
drawShape(parentNode, element) {
const type = element.type // 获取到类型
if (customElements.includes(type)) { // or customConfig[type]
const { url, attr } = customConfig[type]
const customIcon = svgCreate('image', { // 在这里建立了一个image
...attr,
href: url
})
element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
element['height'] = attr.height
svgAppend(parentNode, customIcon)
return customIcon
}
const shape = this.bpmnRenderer.drawShape(parentNode, element)
return shape
}
...
复制代码
能够看到,实现让页面渲染出本身想要的效果的作法就是使用svgCreate
方法建立一个image
并添加到父节点中.
CustomRenderer
一样的自定义renderer
须要导出才能使用, 修改custom/index.js
文件:
import CustomPalette from './CustomPalette'
import CustomRenderer from './CustomRenderer'
export default {
__init__: ['customPalette', 'customRenderer'],
customPalette: ['type', CustomPalette],
customRenderer: ['type', CustomRenderer]
}
复制代码
注意: __init__
中的属性命名customRenderer
都是固定的写法不能修改, 否则就会没有效果
要是你看了以前custom-palette.vue
的话, 就知道直接在页面上应用就好了:
<!--custom-renderer.vue-->
<script> ... import customModule from './custom' ... this.bpmnModeler = new BpmnModeler({ ... additionalModules: [ // 左边工具栏以及节点 propertiesProviderModule, // 自定义的节点 customModule ] }) 复制代码
注意: 项目案例里我为了方便演示, 在custom-palette
中引入的是ImportJS/onlyRenderer.js
, 而上面的案例是以引入custom/index.js
为讲解的, 这个本身要明白如何区分.
此时打开页面就能够看到效果了, 类型为bpmn:Task
的节点就被渲染成了自定义的“黄金积木”😝:
彻底自定义Renderer
的意思就是将本来使用new BpmnModeler
建立画布的方式改成使用new CustomModeler
来建立.
这一部分在《全网最详bpmn.js教材-自定义palette篇》中讲解的很详细了, 就不作过多的阐述.
一样是在customModeler/custom
的文件夹下建立一个customRender.js
文件, 而后写入如下代码:
/* eslint-disable no-unused-vars */
import inherits from 'inherits'
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'
import {
append as svgAppend,
create as svgCreate
} from 'tiny-svg'
import { customElements, customConfig } from '../../utils/util'
/** * A renderer that knows how to render custom elements. */
export default function CustomRenderer(eventBus, styles) {
BaseRenderer.call(this, eventBus, 2000)
var computeStyle = styles.computeStyle
this.drawCustomElements = function(parentNode, element) {
console.log(element)
const type = element.type // 获取到类型
if (customElements.includes(type)) { // or customConfig[type]
const { url, attr } = customConfig[type]
const customIcon = svgCreate('image', {
...attr,
href: url
})
element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
element['height'] = attr.height
svgAppend(parentNode, customIcon)
return customIcon
}
const shape = this.bpmnRenderer.drawShape(parentNode, element)
return shape
}
}
inherits(CustomRenderer, BaseRenderer)
CustomRenderer.$inject = ['eventBus', 'styles']
CustomRenderer.prototype.canRender = function(element) {
// ignore labels
return !element.labelTarget;
}
CustomRenderer.prototype.drawShape = function(p, element) {
return this.drawCustomElements(p, element)
}
CustomRenderer.prototype.getShapePath = function(shape) {
console.log(shape)
}
复制代码
直接修改原型链中的drawShape
方法就能够了.
而后记得在customModeler/custom/index.js
中将其导出.
因为评论区有小伙伴提了问题: 该如何将label
标签自定义在元素的下方?
所以霖呆呆我回去也是花了点时间研究了一下label
标签.
首先label
标签其实是xml
中各个标签上的一个名叫name
的属性, 以下图:
开始节点和lindaidai-task
中都有name
属性, 可是在bpmn:StartEvent
上能将这个label
显示出来, 是由于在下面有一个bpmndi:BPMNLabel
的标签.
因而就形成了图形上是这样显示的:
那么咱们该如何将这里的label
显示出来呢?
首先让咱们先将Shape
打印出来看看:
能够发如今businessObject
中有一个name
属性...
既然这样的话, 咱们确定也能在drawShape
中拿到这个name
属性, 以后能够用svgCreate
方法给父节点中添加一个文本类型的标签.
// CustomRenderer.js
import { hasLabelElements } from '../../utils/util'
drawShape(parentNode, element) {
const type = element.type // 获取到类型
if (customElements.includes(type)) { // or customConfig[type]
const { url, attr } = customConfig[type]
const customIcon = svgCreate('image', {
...attr,
href: url
})
element['width'] = attr.width // 这里我是取了巧, 直接修改了元素的宽高
element['height'] = attr.height
svgAppend(parentNode, customIcon)
// 判断是否有name属性来决定是否要渲染出label
if (!hasLabelElements.includes(type) && element.businessObject.name) {
const text = svgCreate('text', {
x: attr.x,
y: attr.y + attr.height + 20, // y取的是父元素的y+height+20
"font-size": "14",
"fill": "#000"
})
text.innerHTML = element.businessObject.name
svgAppend(parentNode, text)
console.log(text)
}
return customIcon
}
const shape = this.bpmnRenderer.drawShape(parentNode, element)
return shape
}
复制代码
由于有些元素自己就带有label
属性的, 好比bpmn:StartEvent
, 因此不须要从新渲染, 所以我在util.js
中加了一个hasLabelElements
数组:
// utils/util.js
const hasLabelElements = ['bpmn:StartEvent', 'bpmn:EndEvent'] // 一开始就有label标签的元素类型
复制代码
以前我是想经过element.labels.length<=0
来过滤掉开始就有label
标签的元素的, 可是发如今渲染阶段还获取不到labels
, 因此长度一直都会是0
, 就干脆定义一个hasLabelElements
来判断好了😓...
打开页面效果是这样的:
看起来好像成功了 ! good boy ! 😄
可是当我双击想要去编辑label
文字的时候, 却出现了这样的效果:
它直接在我原来图形的上面新建了一个输入框...
额😅...其实我也没有想到什么好的办法去解决,在这里我提供一个看起来可行的方案: 在双击元素的时候, 将text
给移除, 或者将他的innerHTML
设置为''
.
固然你要是感受这样也看得下去的话, 咱不捣鼓也行, 毕竟你编辑这里面的内容, 下面的label
也是会同步的变的.
再不济的话, 你能够全局修改djs-direct-editing-parent
这个类的样式, 将下面的文字给覆盖上也是能够的... 固然感受这个不是一个很好的办法. 在app.css
中写入:
.djs-direct-editing-parent {
top: 130px!important;
width: 60px!important;
}
复制代码
总结
上面的作法主要是利用svgCreate
来建立text
元素添加到parentNode
中, 其实bpmn.js
中用到了不少ting-svg
的东西, 以前也没接触过这些, 而后也是经过查找资料了解到svgCreate
的用法...
科普一波好了, 哈哈😄: SVG基础知识
上面👆案例用的都是同一个项目🦐
项目案例Git地址: LinDaiDai/bpmn-vue-custom
系列相关推荐: