Q: bpmn.js是什麼? 🤔️javascript
」
bpmn.js是一個BPMN2.0渲染工具包和web建模器, 使得畫流程圖的功能在前端來完成.css
Q: 我爲何要寫該系列的教材? 🤔️html
」
由於公司業務的須要於是要在項目中使用到bpmn.js
,可是因爲bpmn.js
的開發者是國外友人, 所以國內對這方面的教材不多, 也沒有詳細的文檔. 因此不少使用方式不少坑都得本身去找.在將其琢磨完以後, 決定寫一系列關於它的教材來幫助更多bpmn.js
的使用者或者是期於找到一種好的繪製流程圖的開發者. 同時也是本身對其的一種鞏固.前端
因爲是系列的文章, 因此更新的可能會比較頻繁, 您要是無心間刷到了且不是您所須要的還請諒解😊.vue
不求贊👍不求心❤️. 只但願能對你有一點小小的幫助.java
在上一章節主要介紹瞭如何在原有properties-panel
的基礎上進行擴展, 可是有不少小夥伴就會說我太嫌棄原有屬性欄的樣式了 😅...我是一名成熟的前端了, 我要有本身的想法...git
OK... 我尊重你...這一章節霖呆呆就來教教你們怎樣美化咱們的properties-panel
😊.github
經過這一章節的閱讀你能夠學習到:web
properties-panel
label
屬性
color
屬性
event
節點類型
Task
節點的類型
properties-panel
並設置一些默認值
先來看看咱們經過修改屬性欄的默認樣式能夠實現什麼樣的效果🤔️吧!canvas
如上👆所示, 你能夠給屬性欄定製不一樣的主題顏色, 來美化它本來的樣子.
其實想要修改默認屬性欄的樣式, 很是簡單, 只要打開控制檯(Window: F12, Mac : option + command + i)經過審查元素, 找到各個元素的class
, 而後在代碼裏覆蓋它原有的屬性就能夠了.
還記得咱們以前在項目的main.js
中引用了properties-panel
的樣式嗎?
// main.js
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右邊工具欄樣式
複製代碼
如今讓咱們在項目中建立一個styles
文件夾, 同時建立一個bpmn-properties-theme-red.css
文件, 裏面將用來編寫咱們須要自定義修改的屬性欄樣式.
以後在main.js
中引用它, 最好是放在原有樣式的後面:
// main.js
import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右邊工具欄樣式
import './styles/bpmn-properties-theme-red.css' // 緋紅主題
複製代碼
好比如今我想要修改一下屬性欄頭部的字體顏色:
經過審查元素找到這個類, 而後在bpmn-properties-theme-red.css
中修改它:
.bpp-properties-header>.label {
color: rgb(239, 112, 96);
font-size: 16px;
}
複製代碼
保存再次打開頁面就能夠看到效果了.
固然我這裏只是演示一下能夠怎樣去修改默認的樣式, 因此只是用了最簡單的css
來演示. 這裏其實有很大的擴展空間, 你能夠用less
或者sass
來編寫, 也能夠本身實現一下主題切換等等的功能. 拋磚引玉但願能給你啓發 😊...
若是你想偷會懶...直接取霖呆呆的樣式也行...
上面👆案例的github
地址:
properties-panel
有時候你可能不知足用官方提供的properties-panel
, 而是想要自定義一個屬性欄, 這也是能夠實現的.
好比我想要根據不一樣的節點類型, 在右邊顯示不一樣的屬性配置, 而且編輯完以後能夠同步更新到xml
上.
其實實現的原理在以前的 《全網最詳bpmn.js教材-properties篇》中也有提過了, 主要是利用updateProperties()
這個方法來修改元素節點上的屬性.
如今就讓咱們來看看如何封裝一個這樣的自定義屬性欄吧😊.
因爲自定義屬性欄的代碼可能會不少, 並且可能還會涉及到不少複雜的業務組件, 因此我建議你將其從引入bpmn.js
的地方給抽離出來, 也就是封裝成一個通用的自定義屬性欄組件.
組件的props
既然決定將其抽離成組件了, 那麼這個組件的props
應該設置成什麼呢?
(props
即父組件向子組件傳遞的值, 在這裏父元素就是引入bpmn.js
的地方, 子元素爲自定義屬性欄組件)
先來讓咱們理理咱們的需求, 咱們須要點擊不一樣的元素來呈現不一樣的配置, 那麼能夠將單個element
做爲props
傳遞進去.
不事後來在編寫的過程當中, 我發現有不少事件的綁定都是要涉及到modeler
的, 如果將這些綁定事件都在父組件中完成不就違背了咱們抽離出單獨組件的意願了嗎🤔️?
因此在這裏, 我是將整個modeler
做爲props
來編寫.這樣不論是給modeler
綁定事件仍是給element
綁定事件都很好作了.
OK...考慮好props
, 讓咱們在components
文件夾下建立一個custom-properties-panel
的文件夾, 並在其中建立一個名爲PropertiesView.vue
的文件, 用來編寫咱們的自定義屬性欄組件.
咱們指望的這個組件是可以這樣在html
中使用:
<div class="containers" ref="content">
<div class="canvas" ref="canvas"></div>
<properties-view v-if="bpmnModeler" :modeler="bpmnModeler"></properties-view>
</div>
複製代碼
(bpmnModeler
是你使用new BpmnModeler
建立的modeler
對象)
先來將這個組件的基礎結構給搭好:
<!--PropertiesView.vue-->
<template>
<div class="custom-properties-panel"></div>
</template>
<script> export default { name: 'PropertiesView', props: { modeler: { type: Object, default: () => ({}) } }, data () { return { selectedElements: [], // 當前選擇的元素集合 element: null // 當前點擊的元素 } }, created () { this.init() }, methods: { init () {} } } </script>
<style scoped></style>
複製代碼
html
代碼先讓我給這個組件裏添加點東西:
<template>
<div class="custom-properties-panel">
<div class="empty" v-if="selectedElements.length<=0">請選擇一個元素</div>
<div class="empty" v-else-if="selectedElements.length>1">只能選擇一個元素</div>
<div v-else>
<fieldset class="element-item">
<label>id</label>
<span>{{ element.id }}</span>
</fieldset>
<fieldset class="element-item">
<label>name</label>
<input :value="element.name" @change="(event) => changeField(event, 'name')" />
</fieldset>
<fieldset class="element-item">
<label>customProps</label>
<input :value="element.name" @change="(event) => changeField(event, 'customProps')" />
</fieldset>
</div>
</div>
</template>
複製代碼
如上👆, 我增長了三個屬性, id, name, customProps
. 同時, 有一個selectedElements
的判斷.
這是由於咱們在操做圖形的時候, 若是你使用command + 左鍵
(window上應該是Ctrl
?)是能夠選擇多個節點的, 這時候就須要作一個判斷.
js
代碼若是你看多了霖呆呆寫的代碼, 你會發現我比較喜歡將一些初始化的代碼提到一個叫作init()
的函數中來, 這個是我的編碼習慣哈...
在這裏, 咱們的初始化函數主要作如下幾件事:
selection.changed
監聽選中的元素;
element.changed
監聽發生改變的元素.
init () {
const { modeler } = this // 父組件傳遞進來的 modeler
modeler.on('selection.changed', e => {
this.selectedElements = e.newSelection // 數組, 可能有多個
this.element = e.newSelection[0] // 默認取第一個
})
modeler.on('element.changed', e => {
const { element } = e
const { element: currentElement } = this
if (!currentElement) {
return
}
// update panel, if currently selected element changed
if (element.id === currentElement.id) {
this.element = element
}
})
}
複製代碼
另外, 咱們能夠寫一個公用的屬性更新方法, 用來更新元素上的屬性:
/** * 更新元素屬性 * @param { Object } 要更新的屬性, 例如 { name: '', id: '' } */
updateProperties(properties) {
const { modeler, element } = this
const modeling = modeler.get('modeling')
modeling.updateProperties(element, properties)
}
複製代碼
而後給屬性欄上的input
或者其它的控件, 增長一個@change
事件, 當控件內的內容發生改變時, 同步更新element
.
/** * 改變控件觸發的事件 * @param { Object } input的Event * @param { String } 要修改的屬性的名稱 */
changeField (event, type) {
const value = event.target.value
let properties = {}
properties[type] = value
this.element[type] = value
this.updateProperties(properties) // 調用屬性更新方法
}
複製代碼
將上面👆的全部代碼組合起來:
<template>
<div class="custom-properties-panel">
<div class="empty" v-if="selectedElements.length<=0">請選擇一個元素</div>
<div class="empty" v-else-if="selectedElements.length>1">只能選擇一個元素</div>
<div v-else>
<fieldset class="element-item">
<label>id</label>
<span>{{ element.id }}</span>
</fieldset>
<fieldset class="element-item">
<label>name</label>
<input :value="element.name" @change="(event) => changeField(event, 'name')" />
</fieldset>
<fieldset class="element-item">
<label>customProps</label>
<input :value="element.name" @change="(event) => changeField(event, 'customProps')" />
</fieldset>
</div>
</div>
</template>
<script> export default { name: 'PropertiesView', props: { modeler: { type: Object, default: () => ({}) } }, data() { return { selectedElements: [], element: null } }, created() { this.init() }, methods: { init() { const { modeler } = this modeler.on('selection.changed', e => { this.selectedElements = e.newSelection this.element = e.newSelection[0] }) modeler.on('element.changed', e => { const { element } = e const { element: currentElement } = this if (!currentElement) { return } // update panel, if currently selected element changed if (element.id === currentElement.id) { this.element = element } }) }, /** * 改變控件觸發的事件 * @param { Object } input的Event * @param { String } 要修改的屬性的名稱 */ changeField(event, type) { const value = event.target.value let properties = {} properties[type] = value this.element[type] = value this.updateProperties(properties) }, updateName(name) { const { modeler, element } = this const modeling = modeler.get('modeling') // modeling.updateLabel(element, name) modeling.updateProperties(element, { name }) }, /** * 更新元素屬性 * @param { Object } 要更新的屬性, 例如 { name: '' } */ updateProperties(properties) { const { modeler, element } = this const modeling = modeler.get('modeling') modeling.updateProperties(element, properties) } } } </script>
<style scoped> /** 更多代碼在git上有, git連接見底部後語 **/ .custom-properties-panel { position: absolute; right: 0; top: 0; width: 300px; background-color: #fff9f9; border-color: rgba(0, 0, 0, 0.09); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09); padding: 20px; } </style>
複製代碼
label
屬性在上面的例子中, 咱們演示了若是修改元素屬性的, 若是你想要修改一個元素的label
, 一種方式是像上面👆同樣, 修改name
這個屬性, 或者用modeling.updateLabel
這個方法更新也是同樣的:
updateName(name) {
const { modeler, element } = this
const modeling = modeler.get('modeling')
modeling.updateLabel(element, name)
// 等同於 modeling.updateProperties(element, { name })
},
複製代碼
color
屬性如何讓用戶手動修改節點的顏色呢?
能夠利用modeling.setColor
這個方法.
好比我在代碼中添加一行屬性:
<fieldset class="element-item">
<label>節點顏色</label>
<input type="color" :value="element.color" @change="(event) => changeField(event, 'color')" />
</fieldset>
複製代碼
而後改造如下changeField
方法:
/** * 改變控件觸發的事件 * @param { Object } input的Event * @param { String } 要修改的屬性的名稱 */
changeField(event, type) {
const value = event.target.value
let properties = {}
properties[type] = value
if (type === 'color') { // 如果color屬性
this.onChangeColor(value)
}
this.element[type] = value
this.updateProperties(properties)
},
onChangeColor(color) {
const { modeler, element } = this
const modeling = this.modeler.get('modeling')
modeling.setColor(element, {
fill: color,
stroke: null
})
},
複製代碼
setColor
這個方法接收兩個屬性:
fill
: 節點的填充色
stroke
: 節點邊框的顏色和節點
label
的顏色
在上面我演示的是修改節點的填充色, 也就是fill
, 固然你也能夠改變stroke
, 效果是這樣的:
有意思的是, 若是你把fill
和stroke
都設置成了color
:
modeling.setColor(element, {
fill: color,
stroke: color
})
複製代碼
那麼label
標籤就看不到了... 這是由於stroke
也會改變label
的顏色, 讓它變得和fill
同樣.
不過通常你也不會將邊框和填充內容
設置成一個色吧...不必...
若是你實在是想要解決這個問題的話, 這裏有個不靠譜的作法, 就是在全局的css
中, 將label
的樣式強行修改一下:
.djs-label {
fill: #000!important;
}
複製代碼
event
節點類型有些時候, 咱們可能還須要在自定義屬性欄中修改這個節點的類型, 好比在開始節點, 點擊contextPad
上的小扳手:
實現這個功能咱們須要用到bpmnReplace.replaceElement
這個方法.
首先讓咱們看看event
裏這個屬性是放在哪裏的.
以下圖: 我修改了一下開始節點的類型, 將它改成MessageEventDefinition
它對應是放在element.businessObject.eventDefinitions
這個數組中的, 如果StartEvent
和EndEvent
, 則這個數組爲undefinded
.
讓咱們來看看這個功能怎麼實現哈 😄.
首先在html
加上修改event節點類型
的下拉框:
<!--PropertiesView.vue-->
<template>
<fieldset class="element-item" v-if="isEvent">
<label>修改event節點類型</label>
<select @change="changeEventType" :value="eventType">
<option v-for="option in eventTypes" :key="option.value" :value="option.value" >{{ option.label }}</option>
</select>
</fieldset>
</template>
<script> export default { data () { return { eventTypes: [ { label: '默認', value: '' }, { label: 'MessageEventDefinition', value: 'bpmn:MessageEventDefinition' }, { label: 'TimerEventDefinition', value: 'bpmn:TimerEventDefinition' }, { label: 'ConditionalEventDefinition', value: 'bpmn:ConditionalEventDefinition' } ], eventType: '' } }, methods: { verifyIsEvent (type) { // 判斷類型是否是event return type.includes('Event') }, changeEventType (event) {} }, computed: { isEvent() { // 判斷當前點擊的element類型是否是event const { element } = this return this.verifyIsEvent(element.type) } } } </script>
複製代碼
好了, 完成上面👆的基礎代碼, 主要邏輯就是在改變下拉框值的時候了:
changeEventType(event) { // 改變下拉框
const { modeler, element } = this
const value = event.target.value
const bpmnReplace = modeler.get('bpmnReplace')
this.eventType = value
bpmnReplace.replaceElement(element, {
type: element.businessObject.$type,
eventDefinitionType: value
})
},
複製代碼
如今改變下拉框的值, 就能夠改變eventDefinitionType
的值了, 不過還有一個問題, 就是你點擊了其它的節點, 而後再次點回開始節點的時候, 下拉框的默認值就不對了, 也就是說咱們還須要獲取到這個開始節點自己的eventDefinitionType
值.
這時候, 咱們能夠在selection.changed
監聽事件中作這類初始化properties-panel
的事情.
init () {
modeler.on('selection.changed', e => {
this.selectedElements = e.newSelection
this.element = e.newSelection[0]
console.log(this.element)
this.setDefaultProperties() // 設置一些默認的值
})
}
setDefaultProperties() {
const { element } = this
if (element) {
const { type, businessObject } = element
if (this.verifyIsEvent(type)) { // 如果event類型
// 獲取默認的 eventDefinitionType
this.eventType = businessObject.eventDefinitions ? businessObject.eventDefinitions[0]['$type'] : ''
}
}
}
複製代碼
Task
節點的類型event
類型的節點咱們已經知道怎麼修改了, 那麼對於Task
類型的節點呢 🤔️?
其實作法都差很少.
一樣, 讓咱們在html
中加上針對Task
類型的屬性下拉框:
<!--PropertiesView.vue-->
<template>
<fieldset class="element-item" v-if="isTask">
<label>修改Task節點類型</label>
<select @change="changeTaskType" :value="taskType">
<option v-for="option in taskTypes" :key="option.value" :value="option.value" >{{ option.label }}</option>
</select>
</fieldset>
</template>
<script> export default { data () { return { taskTypes: [ { label: 'Task', value: 'bpmn:Task' }, { label: 'ServiceTask', value: 'bpmn:ServiceTask' }, { label: 'SendTask', value: 'bpmn:SendTask' }, { label: 'UserTask', value: 'bpmn:UserTask' } ], taskType: '' } }, methods: { verifyIsTask(type) { return type.includes('Task') }, changeTaskType (event) {} }, computed: { isTask() { // 判斷當前點擊的element類型是否是task const { element } = this return this.verifyIsTask(element.type) } } } </script>
複製代碼
而後在改變Task
下拉框的時候:
changeTaskType(event) {
const { modeler, element } = this
const value = event.target.value // 當前下拉框選擇的值
const bpmnReplace = modeler.get('bpmnReplace')
bpmnReplace.replaceElement(element, {
type: value // 直接修改type就能夠了
})
}
複製代碼
properties-panel
並設置一些默認值咱們在設置本身的自定義屬性欄的時候, 可能要根據不一樣的節點類型來作不一樣的業務邏輯判斷, 並對properties-panel
作一些默認值的設置, 好比上面👆的修改event類型
, 這時候咱們能夠怎麼樣作呢 🤔️?
和修改event類型
同樣, 咱們能夠在selection.changed
監聽事件中完成這個功能.
init () {
modeler.on('selection.changed', e => {
this.selectedElements = e.newSelection
this.element = e.newSelection[0]
console.log(this.element)
this.setDefaultProperties() // 設置一些默認的值
})
}
setDefaultProperties() {
const { element } = this
if (element) {
// 這裏能夠拿到當前點擊的節點的全部屬性
const { type, businessObject } = element
// doSomeThing
}
}
複製代碼
其實就是和上面👆介紹修改event類型
的初始化同樣, 不過我怕有的小夥伴直接跳過了修改event類型
沒有看到這一部分, 因此單獨拎出來講下.
replace
的類型在上面👆咱們介紹了關於Event
和Task
類型的元素是如何轉化類型的, 案例中也僅僅演示了幾種類型, 那麼所有的類型到哪裏看呢 🤔️?
你能夠在bpmn.js
的源碼這裏找到:
https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/replace/ReplaceOptions.js
複製代碼
你甚至能夠直接到代碼中將裏面你要的內容導出:
import { START_EVENT } from 'bpmn-js/lib/features/replace/ReplaceOptions.js'
複製代碼
上面👆教材案例的代碼地址: LinDaiDai/bpmn-vue-properties-panel
截止到本章節, properties-panel
算是介紹大概了, 不論是要使用原有的properties-panel
仍是使用自定義properties-panel
我相信你都已經掌握了 😄...
在後續霖呆呆可能會根據bpmn.js
源碼來列舉一些經常使用的屬性和方法, 以便你更好的瞭解bpmn.js
.
立刻要過年了🧨了...
碼完了這一章節, 霖呆呆也要開始整理回家的行李了 😄 ...
再次祝你們新年快樂呀~ 🔥 🎆
最後, 若是你也對bpmn.js
感興趣能夠進咱們的bpmn.js交流羣👇👇👇, 共同窗習, 共同進步.
關注霖呆呆的公衆號, 選擇「其它」菜單中的「bpmn.js羣」便可😊.
系列所有目錄請查看此處: 《全網最詳bpmn.js教材》
系列相關推薦: