全網最詳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教材-事件篇》

相關文章
相關標籤/搜索