前言
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
等等.
還是先來看一張圖了解一下我們的繪圖頁面都有哪些東西:
這一章我要介紹的時候如何自定義左側(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é)點
效果是這樣的:
如上所示, 只改變了任務(wù)框的顏色為紅色, 所以效果不是很明顯, 你甚至可以直接給它換一個樣貌:
接下來讓我們看看該怎么實現(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):
我已經(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ā)的事件
接下來我們要做的無非就是:
- 通過
className
來設(shè)置樣式 - 通過
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)的效果:
由于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'
}
這樣頁面上顯示的就是你定義的那張背景圖了:
編寫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
元素了.
我們看到雖然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é)點, 而是全都是自己自定義的, 就不能滿足了. 比如這樣:
此時你就需要重寫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)變成了:
編寫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>
快來打開頁面看看效果:
后語
上面??兩個案例用的都是同一個項目??
項目案例Git地址: LinDaiDai/bpmn-vue-custom 喜歡的小伙伴請給個Star
??呀, 謝謝??
系列全部目錄請查看此處: 《全網(wǎng)最詳bpmn.js教材》
系列相關(guān)推薦:
《全網(wǎng)最詳bpmn.js教材-基礎(chǔ)篇》
《全網(wǎng)最詳bpmn.js教材-renderer篇》