全網(wǎng)最詳bpmn.js教材-自定義palette篇

前言

Q: bpmn.js是什么? ???

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

Q: 我為什么要寫該系列的教材? ???

因為公司業(yè)務(wù)的需要因而要在項目中使用到bpmn.js,但是由于bpmn.js的開發(fā)者是國外友人, 因此國內(nèi)對這方面的教材很少, 也沒有詳細的文檔. 所以很多使用方式很多坑都得自己去找.在將其琢磨完之后, 決定寫一系列關(guān)于它的教材來幫助更多bpmn.js的使用者或者是期于找到一種好的繪制流程圖的開發(fā)者. 同時也是自己對其的一種鞏固.

由于是系列的文章, 所以更新的可能會比較頻繁, 您要是無意間刷到了且不是您所需要的還請諒解??.

不求贊??不求心??. 只希望能對你有一點小小的幫助.

所有教材的github地址: 《bpmn-chinese-document》

自定義Palette篇

經(jīng)過前面幾章的基礎(chǔ)教程相信大家對bpmn.js的基本使用已經(jīng)有了一個很好的掌握.

從這一章節(jié)開始我會講解一些關(guān)于bpmn.js中自定義的部分, 包括自定義左側(cè)工具欄、自定義渲染、自定義contextPad等等.

還是先來看一張圖了解一下我們的繪圖頁面都有哪些東西:

image

這一章我要介紹的時候如何自定義左側(cè)的工具欄(Palette, 也叫調(diào)色板), 通過閱讀你可以學(xué)習(xí)到:

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

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

在默認(rèn)的Palette基礎(chǔ)上修改

先來看看第一種最簡單的, 我們在官方提供的調(diào)色板里新增一個自定義的項.

  • 元素類型: bpmn:Task
  • 元素名稱: lindaidai-task
  • 樣式: 沿用bpmn:Task原有的樣式, 只不過將邊框變?yōu)榧t色
  • 作用: 創(chuàng)建一個類型為lindaidai-task的任務(wù)節(jié)點

效果是這樣的:

bpmnCustom1.png

如上所示, 只改變了任務(wù)框的顏色為紅色, 所以效果不是很明顯, 你甚至可以直接給它換一個樣貌:

bpmnCustom2.png

接下來讓我們看看該怎么實現(xiàn)它吧!??

前期準(zhǔn)備

因為是新的章節(jié), 這里我也新建一個項目:

$ vue create bpmn-vue-custom
$ npm i vue-router axios bpmn-js-properties-panel bpmn-js --save-D

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

components文件夾下新建一個名為custom-palette.vue的文件, 并將provider.vue(之前的一個基礎(chǔ)案例) 的內(nèi)容復(fù)制進去.

繼續(xù)在components文件夾下新建文件夾custom用于盛放我們后面要寫的一些自定義的東西.

來看看我們現(xiàn)在的項目結(jié)構(gòu):

image

我已經(jīng)在custom文件夾新建立了一個CustomPalette.js, 接下來就是要在這里面寫上我們要自定義的項.

編寫CustomPalette.js代碼

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

這里我就取為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);
    }
    // 這個函數(shù)就是繪制palette的核心
    getPaletteEntries(element) {}
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]

上面??的代碼很好理解:

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

定義完CustomPalette.js之后, 我們需要在其同級的index.js中將它導(dǎo)出:

// 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: [
        // 左邊工具欄以及節(jié)點
        propertiesProviderModule,
        // 自定義的節(jié)點
        customModule
    ]
})

</script>

編寫核心函數(shù)getPaletteEntries代碼

拋開這些不看, 重點就是如何構(gòu)造這個getPaletteEntries函數(shù)

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

// CustomPalette.js
getPaletteEntries(element) {
    return {
        'create.lindaidai-task': {
            group: 'model', // 分組名
            className: 'bpmn-icon-task red', // 樣式類名
            title: translate('創(chuàng)建一個類型為lindaidai-task的任務(wù)節(jié)點'),
            action: { // 操作
                dragstart: createTask(), // 開始拖拽時調(diào)用的事件
                click: createTask() // 點擊時調(diào)用的事件
            }
        }
    }
}

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

  • group: 屬于哪個分組, 比如tools狠毯、event、gateway晶疼、activity等等,用于分類
  • className: 樣式類名, 我們可以通過它給元素修改樣式
  • title: 鼠標(biāo)移動到元素上面給出的提示信息
  • action: 用戶操作時會觸發(fā)的事件

接下來我們要做的無非就是:

  1. 通過className來設(shè)置樣式
  2. 通過action來定義要觸發(fā)的事情

編寫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類, 使用它就可以實現(xiàn)一個task的圖標(biāo)的效果:

image

由于iconfont是一個字體, 所以這里我使用color來改變它的顏色.

如果你想要給它完全換一張圖片的話也可以用className來實現(xiàn):

/* 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代碼

完成了上面的操作, 其實頁面已經(jīng)能正常渲染出一個我們自定義的元素了, 但是你在點擊或者拖拽它的時候是沒有效果的??.

此時我們期望的是點擊或者拖拽它能在畫布中畫出一個lindaidai-task, 因此你得給它加上事件,
也就是編寫一個函數(shù)用來創(chuàng)建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) // 只在拖動或者點擊時觸發(fā)
        create.start(event, shape);
    }
}

這里的核心其實就是利用bpmn.js提供的一些方法創(chuàng)建shape然后將其添加到畫布上.

(我這里演示的是創(chuàng)建一個類型為bpmn:Task的元素, 你還可以用來創(chuàng)建bpmn:StartEvent、bpmn:ServiceTask又憨、bpmn:ExclusiveGateway等等...)

此時你拖動或者點擊lindaidai-task就可以在頁面上創(chuàng)建一個Task元素了.

image

我們看到雖然lindaidai-task在左側(cè)工具欄中是金黃金黃的, 但是實際畫到頁面卻還是呈現(xiàn)“裸體”狀態(tài)??, 這就和自定義渲染有關(guān)系了, 不要著急, 這些在后面的章節(jié)中會講到.

完整的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) // 只在拖動或者點擊時觸發(fā)
                create.start(event, shape);
            }
        }

        return {
            'create.lindaidai-task': {
                group: 'model',
                className: 'icon-custom lindaidai-task',
                title: translate('創(chuàng)建一個類型為lindaidai-task的任務(wù)節(jié)點'),
                action: {
                    dragstart: createTask(),
                    click: createTask()
                }
            }
        }
    }
}

CustomPalette.$inject = [
    'bpmnFactory',
    'create',
    'elementFactory',
    'palette',
    'translate'
]

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

注意: 項目案例里我為了方便演示, 在custom-palette中引入的是ImportJS/onlyPalette.js, 而上面的案例是以引入custom/index.js為講解的, 這個自己要明白如何區(qū)分.

完全自定義Palette

可以看到, 上面??的那種實現(xiàn)方式實際上就是定義了一個CustomPalette然后在new BpmnModeler生成的對象中引用進去.

但是這樣做有一點不好??, 那就是如果你不想要它提供的默認(rèn)的這些項, 比如開始節(jié)點翠霍、結(jié)束節(jié)點、任務(wù)節(jié)點, 而是全都是自己自定義的, 就不能滿足了. 比如這樣:

bpmnCustom6.png

此時你就需要重寫BpmnModeler這個類了, 實現(xiàn)自己獨有的一套modeler.

前期準(zhǔn)備

繼續(xù)在上面??的項目的基礎(chǔ)上創(chuàng)建一個customModeler文件夾和一個custom-modeler.vue文件.
然后在customModeler中創(chuàng)建一個index.js和一個custom文件夾.

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

此時項目結(jié)構(gòu)變成了:

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) // 只在拖動或者點擊時觸發(fā)
            create.start(event, shape);
        }
    }

    return {
        'create.lindaidai-task': {
            group: 'model',
            className: 'icon-custom lindaidai-task',
            title: '創(chuàng)建一個類型為lindaidai-task的任務(wù)節(jié)點',
            action: {
                dragstart: createTask(),
                click: createTask()
            }
        }
    }
}

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

(別看上面??寫的東西好像很多的樣子, 但是其實靜下心來看發(fā)現(xiàn)也沒啥??)

編寫custom/index.js代碼

接下來還是和第一種方式一樣, 需要將我們自定義的Palette導(dǎo)出:

// 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
    ]
)

導(dǎo)出的類繼承了Modeler這個核心的類, 這樣就保證了其他功能的實現(xiàn).

在頁面上引用

最后一步, 是需要將我們原本通過BpmnModeler創(chuàng)建的對象改為通過我們自定義的CustomModeler來創(chuàng)建, 編寫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 喜歡的小伙伴請給個Star??呀, 謝謝??

系列全部目錄請查看此處: 《全網(wǎng)最詳bpmn.js教材》

系列相關(guān)推薦:

《全網(wǎng)最詳bpmn.js教材-基礎(chǔ)篇》

《全網(wǎng)最詳bpmn.js教材-http請求篇》

《全網(wǎng)最詳bpmn.js教材-事件篇》

《全網(wǎng)最詳bpmn.js教材-renderer篇》

《全網(wǎng)最詳bpmn.js教材-contextPad篇》

《全網(wǎng)最詳bpmn.js教材-編輯蠢莺、刪除節(jié)點篇》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寒匙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子浪秘,更是在濱河造成了極大的恐慌蒋情,老刑警劉巖埠况,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耸携,死亡現(xiàn)場離奇詭異,居然都是意外死亡辕翰,警方通過查閱死者的電腦和手機夺衍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喜命,“玉大人沟沙,你說我怎么就攤上這事”陂牛” “怎么了矛紫?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長牌里。 經(jīng)常有香客問我颊咬,道長,這世上最難降的妖魔是什么牡辽? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任喳篇,我火速辦了婚禮,結(jié)果婚禮上态辛,老公的妹妹穿的比我還像新娘麸澜。我一直安慰自己,他們只是感情好奏黑,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布炊邦。 她就那樣靜靜地躺著编矾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪馁害。 梳的紋絲不亂的頭發(fā)上洽沟,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音蜗细,去河邊找鬼裆操。 笑死,一個胖子當(dāng)著我的面吹牛炉媒,可吹牛的內(nèi)容都是我干的踪区。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼吊骤,長吁一口氣:“原來是場噩夢啊……” “哼缎岗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起白粉,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤传泊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鸭巴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眷细,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年鹃祖,在試婚紗的時候發(fā)現(xiàn)自己被綠了溪椎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡恬口,死狀恐怖校读,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情祖能,我是刑警寧澤歉秫,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站养铸,受9級特大地震影響雁芙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揭厚,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一却特、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筛圆,春花似錦裂明、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扳碍。三九已至,卻和暖如春仙蛉,著一層夾襖步出監(jiān)牢的瞬間笋敞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工荠瘪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夯巷,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓哀墓,卻偏偏與公主長得像趁餐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子篮绰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容