Q: bpmn.js是什麼? 🤔️javascript
bpmn.js是一個BPMN2.0渲染工具包和web建模器, 使得畫流程圖的功能在前端來完成.css
Q: 我爲何要寫該系列的教材? 🤔️html
由於公司業務的須要於是要在項目中使用到bpmn.js
,可是因爲bpmn.js
的開發者是國外友人, 所以國內對這方面的教材不多, 也沒有詳細的文檔. 因此不少使用方式不少坑都得本身去找.在將其琢磨完以後, 決定寫一系列關於它的教材來幫助更多bpmn.js
的使用者或者是期於找到一種好的繪製流程圖的開發者. 同時也是本身對其的一種鞏固.前端
因爲是系列的文章, 因此更新的可能會比較頻繁, 您要是無心間刷到了且不是您所須要的還請諒解😊.vue
不求贊👍不求心❤️. 只但願能對你有一點小小的幫助.java
接着上一章節, 咱們已經知道了該如何自定義左側的工具欄(Palette), 不瞭解的小夥伴能夠移步: 《全網最詳bpmn.js教材-自定義palette篇》.git
可是同時咱們也知道僅僅只改變Palette
是不夠的, 由於繪畫出來的圖形仍是「裸體的」:github
這一章節咱們就來看一下如何自定義畫布上的圖形, 也就是實現自定義Renderer
的功能.web
經過閱讀你能夠學習到:數組
和自定義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
的節點就被渲染成了自定義的「黃金積木」😝:
徹底自定義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
標籤其實是xml
中各個標籤上的一個名叫name
的屬性, 以下圖:
開始節點和lindaidai-task
中都有name
屬性, 可是在bpmn:StartEvent
上能將這個label
顯示出來, 是由於在下面有一個bpmndi:BPMNLabel
的標籤.
因而就形成了圖形上是這樣顯示的:
那麼咱們該如何將這裏的label
顯示出來呢?
首先讓咱們先將Shape
打印出來看看:
能夠發如今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
來判斷好了😓...
打開頁面效果是這樣的:
看起來好像成功了 ! 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
系列相關推薦: