全網最詳bpmn.js教材-自定義palette篇

前言

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

bpmn.js是一個BPMN2.0渲染工具包和web建模器, 使得畫流程圖的功能在前端來完成.css

Q: 我爲何要寫該系列的教材? 🤔️html

由於公司業務的須要於是要在項目中使用到bpmn.js,可是因爲bpmn.js的開發者是國外友人, 所以國內對這方面的教材不多, 也沒有詳細的文檔. 因此不少使用方式不少坑都得本身去找.在將其琢磨完以後, 決定寫一系列關於它的教材來幫助更多bpmn.js的使用者或者是期於找到一種好的繪製流程圖的開發者. 同時也是本身對其的一種鞏固.前端

因爲是系列的文章, 因此更新的可能會比較頻繁, 您要是無心間刷到了且不是您所須要的還請諒解😊.vue

不求贊👍不求心❤️. 只但願能對你有一點小小的幫助.java

自定義Palette篇

通過前面幾章的基礎教程相信你們對bpmn.js的基本使用已經有了一個很好的掌握.ios

從這一章節開始我會講解一些關於bpmn.js中自定義的部分, 包括自定義左側工具欄、自定義渲染、自定義contextPad等等.git

仍是先來看一張圖瞭解一下咱們的繪圖頁面都有哪些東西:github

這一章我要介紹的時候如何自定義左側的工具欄(Palette, 也叫調色板), 經過閱讀你能夠學習到:web

對於上面👆的目錄, 其實隱含意思就是自定義Palette包括兩種方式:

  • bpmn.js默認提供的Palette上進行修改(或者新增新的項)
  • 徹底覆蓋Palette中有的全部項, 自定義一個全新的Palette

在默認的Palette基礎上修改

先來看看第一種最簡單的, 咱們在官方提供的調色板裏新增一個自定義的項.

  • 元素類型: bpmn:Task
  • 元素名稱: lindaidai-task
  • 樣式: 沿用bpmn:Task原有的樣式, 只不過將邊框變爲紅色
  • 做用: 建立一個類型爲lindaidai-task的任務節點

效果是這樣的:

bpmnCustom1.png

如上所示, 只改變了任務框的顏色爲紅色, 因此效果不是很明顯, 你甚至能夠直接給它換一個樣貌:

bpmnCustom2.png

接下來讓咱們看看該怎麼實現它吧!🐶

前期準備

由於是新的章節, 這裏我也新建一個項目:

$ vue create bpmn-vue-custom
$ npm i vue-router axios bpmn-js-properties-panel bpmn-js --save-D
複製代碼

按照以前的案例LinDaiDai/bpmn-vue-basic配置好相應的路由之類的東西.

components文件夾下新建一個名爲custom-palette.vue的文件, 並將provider.vue(以前的一個基礎案例) 的內容複製進去.

繼續在components文件夾下新建文件夾custom用於盛放咱們後面要寫的一些自定義的東西.

來看看咱們如今的項目結構:

我已經在custom文件夾新創建了一個CustomPalette.js, 接下來就是要在這裏面寫上咱們要自定義的項.

編寫CustomPalette.js代碼

首先這個js是導出一個類(類的名稱你能夠隨意取, 可是在引用的時候不能隨意取, 後面會說到):

這裏我就取爲CustomPalette:

// CustomPalette.js
export default class CustomPalette {
    constructor(bpmnFactory, create, elementFactory, palette, translate) {
        this.bpmnFactory = bpmnFactory;
        this.create = create;
        this.elementFactory = elementFactory;
        this.translate = translate;

        palette.registerProvider(this);
    }
    // 這個函數就是繪製palette的核心
    getPaletteEntries(element) {}
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]
複製代碼

上面👆的代碼很好理解:

  • 定義一個類
  • 使用$inject注入一些須要的變量
  • 在類中使用palette.registerProvider(this)指定這是一個palette

定義完CustomPalette.js以後, 咱們須要在其同級的index.js中將它導出:

// custom/index.js
import CustomPalette from './CustomPalette'

export default {
    __init__: ['customPalette'],
    customPalette: ['type', CustomPalette]
}
複製代碼

注:️ 這裏__init__中的名字就必須是customPalette了, 還有下面的屬性名也必須是customPalette, 否則就會報錯了.

同時要在頁面中使用它:

<!--custom-palette.vue-->
<script> ... import customModule from './custom' ... this.bpmnModeler = new BpmnModeler({ ... additionalModules: [ // 左邊工具欄以及節點 propertiesProviderModule, // 自定義的節點 customModule ] }) </script>
複製代碼

編寫核心函數getPaletteEntries代碼

拋開這些不看, 重點就是如何構造這個getPaletteEntries函數

函數的名稱你不能變, 否則會報錯, 首先它返回的是一個對象, 對象中指定的就是你要自定義的項, 它大概長成這樣:

// CustomPalette.js
getPaletteEntries(element) {
    return {
        'create.lindaidai-task': {
            group: 'model', // 分組名
            className: 'bpmn-icon-task red', // 樣式類名
            title: translate('建立一個類型爲lindaidai-task的任務節點'),
            action: { // 操做
                dragstart: createTask(), // 開始拖拽時調用的事件
                click: createTask() // 點擊時調用的事件
            }
        }
    }
}
複製代碼

能夠看到我定義的一項的名稱就是: create.lindaidai-task. 它會有幾個固定的屬性:

  • group: 屬於哪一個分組, 好比tools、event、gateway、activity等等,用於分類
  • className: 樣式類名, 咱們能夠經過它給元素修改樣式
  • title: 鼠標移動到元素上面給出的提示信息
  • action: 用戶操做時會觸發的事件

接下來咱們要作的無非就是:

  1. 經過className來設置樣式
  2. 經過action來定義要觸發的事情

編寫className代碼

我在scr的目錄下新建了一個css文件, 裏面用來盛放一些全局的樣式, 並在main.js中引用這個全局樣式:

// main.js
// 引入全局的css
import './css/app.css'
複製代碼

而後在其中加上一下樣式:

/* app.css */
.bpmn-icon-task.red {
    color: #cc0000 !important;
}
複製代碼

上面👆的className我之因此要用bpmn-icon-task, 是由於這個類是bpmn.js中自帶的一個iconfont類, 使用它就能夠實現一個task的圖標的效果:

因爲iconfont是一個字體, 因此這裏我使用color來改變它的顏色.

若是你想要給它徹底換一張圖片的話也能夠用className來實現:

/* app.css */
.icon-custom { /* 定義一個公共的類名 */
    border-radius: 50%;
    background-size: 65%;
    background-repeat: no-repeat;
    background-position: center;
}

.icon-custom.lindaidai-task { /* 加上背景圖 */
    background-image: url('https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png');
}
複製代碼

而後修改create.lindaidai-task中的className:

// CustomPalette.js
 'create.lindaidai-task': {
    className: 'icon-custom lindaidai-task' 
 }
複製代碼

這樣頁面上顯示的就是你定義的那張背景圖了:

bpmnCustom4.png

編寫action代碼

完成了上面的操做, 其實頁面已經能正常渲染出一個咱們自定義的元素了, 可是你在點擊或者拖拽它的時候是沒有效果的💦.

此時咱們指望的是點擊或者拖拽它能在畫布中畫出一個lindaidai-task, 所以你得給它加上事件, 也就是編寫一個函數用來建立bpmn:Task這個元素:

// CustomPalette.js
function createTask() {
    return function(event) {
        const businessObject = bpmnFactory.create('bpmn:Task');
        const shape = elementFactory.createShape({
            type: 'bpmn:Task',
            businessObject
        });
        console.log(shape) // 只在拖動或者點擊時觸發
        create.start(event, shape);
    }
}
複製代碼

這裏的核心其實就是利用bpmn.js提供的一些方法建立shape而後將其添加到畫布上.

(我這裏演示的是建立一個類型爲bpmn:Task的元素, 你還能夠用來建立bpmn:StartEvent、bpmn:ServiceTask、bpmn:ExclusiveGateway等等...)

此時你拖動或者點擊lindaidai-task就能夠在頁面上建立一個Task元素了.

咱們看到雖然lindaidai-task在左側工具欄中是金黃金黃的, 可是實際畫到頁面卻仍是呈現「裸體」狀態😅, 這就和自定義渲染有關係了, 不要着急, 這些在後面的章節中會講到.

完整的CustomPalette.js代碼

讓咱們將上面的全部代碼整合一下:

// CustomPalette.js
export default class CustomPalette {
    constructor(bpmnFactory, create, elementFactory, palette, translate) {
        this.bpmnFactory = bpmnFactory;
        this.create = create;
        this.elementFactory = elementFactory;
        this.translate = translate;

        palette.registerProvider(this);
    }

    getPaletteEntries(element) {
        const {
            bpmnFactory,
            create,
            elementFactory,
            translate
        } = this;

        function createTask() {
            return function(event) {
                const businessObject = bpmnFactory.create('bpmn:Task'); // 其實這個也能夠不要
                const shape = elementFactory.createShape({
                    type: 'bpmn:Task',
                    businessObject
                });
                console.log(shape) // 只在拖動或者點擊時觸發
                create.start(event, shape);
            }
        }

        return {
            'create.lindaidai-task': {
                group: 'model',
                className: 'icon-custom lindaidai-task',
                title: translate('建立一個類型爲lindaidai-task的任務節點'),
                action: {
                    dragstart: createTask(),
                    click: createTask()
                }
            }
        }
    }
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]
複製代碼

項目案例Git地址: LinDaiDai/bpmn-vue-custom

徹底自定義Palette

能夠看到, 上面👆的那種實現方式實際上就是定義了一個CustomPalette而後在new BpmnModeler生成的對象中引用進去.

可是這樣作有一點很差👎, 那就是若是你不想要它提供的默認的這些項, 好比開始節點、結束節點、任務節點, 而是全都是本身自定義的, 就不能知足了. 好比這樣:

bpmnCustom6.png

此時你就須要重寫BpmnModeler這個類了, 實現本身獨有的一套modeler.

前期準備

繼續在上面👆的項目的基礎上建立一個customModeler文件夾和一個custom-modeler.vue文件. 而後在customModeler中建立一個index.js和一個custom文件夾.

  • customModeler文件夾下的文件就是用來放自定義的modeler
  • custom-modeler.vue做爲頁面展現(記得配置頁面的路由)

此時項目結構變成了:

bpmnCustom7.png

編寫CustomPalette.js代碼

這裏的CustomPalette.js的編寫方式就和第一種的有所不一樣了:

/** * A palette that allows you to create BPMN _and_ custom elements. */
export default function PaletteProvider(palette, create, elementFactory, globalConnect) {
    this.create = create
    this.elementFactory = elementFactory
    this.globalConnect = globalConnect

    palette.registerProvider(this)
}

PaletteProvider.$inject = [
    'palette',
    'create',
    'elementFactory',
    'globalConnect'
]

PaletteProvider.prototype.getPaletteEntries = function(element) { // 此方法和上面案例的同樣
    const {
        create,
        elementFactory
    } = this;

    function createTask() {
        return function(event) {
            const shape = elementFactory.createShape({
                type: 'bpmn:Task'
            });
            console.log(shape) // 只在拖動或者點擊時觸發
            create.start(event, shape);
        }
    }

    return {
        'create.lindaidai-task': {
            group: 'model',
            className: 'icon-custom lindaidai-task',
            title: '建立一個類型爲lindaidai-task的任務節點',
            action: {
                dragstart: createTask(),
                click: createTask()
            }
        }
    }
}
複製代碼

在這裏是直接重寫了PaletteProvider這個類, 同時覆蓋了其原型上的getPaletteEntries方法, 從而達到覆蓋原有的工具欄的效果.

(別看上面👆寫的東西好像不少的樣子, 可是其實靜下心來看發現也沒啥😊)

編寫custom/index.js代碼

接下來仍是和第一種方式同樣, 須要將咱們自定義的Palette導出:

// custom/index.js
import CustomPalette from './CustomPalette'

export default {
    __init__: ['paletteProvider'],
    paletteProvider: ['type', CustomPalette]
}
複製代碼

這不過這裏咱們就不是用customPalette了, 而是直接用paletteProvider.

編寫customModeler/index.js代碼

最重要的一步, 就是編寫CustomModeler這個類了:

import Modeler from 'bpmn-js/lib/Modeler'

import inherits from 'inherits'

import CustomModule from './custom'

export default function CustomModeler(options) {
    Modeler.call(this, options)

    this._customElements = []
}

inherits(CustomModeler, Modeler)

CustomModeler.prototype._modules = [].concat(
    CustomModeler.prototype._modules, [
        CustomModule
    ]
)
複製代碼

導出的類繼承了Modeler這個核心的類, 這樣就保證了其餘功能的實現.

在頁面上引用

最後一步, 是須要將咱們本來經過BpmnModeler建立的對象改成經過咱們自定義的CustomModeler來建立, 編寫custom-modeler.vue.

<!--custom-modeler.vue-->
<script> ... import CustomModeler from './customModeler' ... this.bpmnModeler = new CustomModeler({ // 本來是用BpmnModeler ... additionalModules: [] // 能夠不用引用任何東西 }) </script>
複製代碼

快來打開頁面看看效果:

bpmnCustom8.png

後語

上面👆兩個案例用的都是同一個項目🦐

項目案例Git地址: LinDaiDai/bpmn-vue-custom

系列相關推薦:

《全網最詳bpmn.js教材-基礎篇》

《全網最詳bpmn.js教材-http請求篇》

《全網最詳bpmn.js教材-事件篇》

相關文章
相關標籤/搜索