全网最详bpmn.js教材-自定义renderer篇

前言

Q: bpmn.js是什么? 🤔️javascript

bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.css

Q: 我为何要写该系列的教材? 🤔️html

由于公司业务的须要于是要在项目中使用到bpmn.js,可是因为bpmn.js的开发者是国外友人, 所以国内对这方面的教材不多, 也没有详细的文档. 因此不少使用方式不少坑都得本身去找.在将其琢磨完以后, 决定写一系列关于它的教材来帮助更多bpmn.js的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是本身对其的一种巩固.前端

因为是系列的文章, 因此更新的可能会比较频繁, 您要是无心间刷到了且不是您所须要的还请谅解😊.vue

不求赞👍不求心❤️. 只但愿能对你有一点小小的帮助.java

自定义Renderer篇

接着上一章节, 咱们已经知道了该如何自定义左侧的工具栏(Palette), 不了解的小伙伴能够移步: 《全网最详bpmn.js教材-自定义palette篇》.git

可是同时咱们也知道仅仅只改变Palette是不够的, 由于绘画出来的图形仍是“裸体的”:github

这一章节咱们就来看一下如何自定义画布上的图形, 也就是实现自定义Renderer的功能.web

经过阅读你能够学习到:数组

在默认的Renderer基础上修改

和自定义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的节点就被渲染成了自定义的“黄金积木”😝:

bpmnCustom9.png

彻底自定义Renderer

彻底自定义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标签.

首先label标签其实是xml中各个标签上的一个名叫name的属性, 以下图:

开始节点和lindaidai-task中都有name属性, 可是在bpmn:StartEvent上能将这个label显示出来, 是由于在下面有一个bpmndi:BPMNLabel的标签.

因而就形成了图形上是这样显示的:

bpmn11.png

那么咱们该如何将这里的label显示出来呢?

首先让咱们先将Shape打印出来看看:

bpmn12.png

能够发如今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来判断好了😓...

打开页面效果是这样的:

bpmn13.png

看起来好像成功了 ! 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

系列相关推荐:

《全网最详bpmn.js教材-基础篇》

《全网最详bpmn.js教材-http请求篇》

《全网最详bpmn.js教材-事件篇》

相关文章
相关标签/搜索