全網最詳bpmn.js教材-properties-panel篇(下)

前言

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

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

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

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

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

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

Properties-panel篇(下)

在上一章節主要介紹瞭如何在原有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上.

自定義properties-panel
自定義properties-panel

其實實現的原理在以前的 《全網最詳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對象)

編寫自定義屬性欄組件

1. 組件結構

先來將這個組件的基礎結構給搭好:

<!--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>
複製代碼

2. 組件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?)是能夠選擇多個節點的, 這時候就須要作一個判斷.

3. 組件的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) // 調用屬性更新方法
}
複製代碼

4. 完整的組件代碼

將上面👆的全部代碼組合起來:

<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屬性

如何讓用戶手動修改節點的顏色呢?

設置fill
設置fill

能夠利用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, 效果是這樣的:

設置stroke
設置stroke

有意思的是, 若是你把fillstroke都設置成了color:

modeling.setColor(element, {
    fill: color,
    stroke: color
})
複製代碼

那麼label標籤就看不到了... 這是由於stroke也會改變label的顏色, 讓它變得和fill同樣.

設置fill和stroke
設置fill和stroke

不過通常你也不會將邊框和填充內容設置成一個色吧...不必...

若是你實在是想要解決這個問題的話, 這裏有個不靠譜的作法, 就是在全局的css中, 將label的樣式強行修改一下:

.djs-label {
    fill: #000!important;
}
複製代碼

修改event節點類型

有些時候, 咱們可能還須要在自定義屬性欄中修改這個節點的類型, 好比在開始節點, 點擊contextPad上的小扳手:

實現這個功能咱們須要用到bpmnReplace.replaceElement這個方法.

首先讓咱們看看event裏這個屬性是放在哪裏的.

以下圖: 我修改了一下開始節點的類型, 將它改成MessageEventDefinition

它對應是放在element.businessObject.eventDefinitions這個數組中的, 如果StartEventEndEvent, 則這個數組爲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的類型

在上面👆咱們介紹了關於EventTask類型的元素是如何轉化類型的, 案例中也僅僅演示了幾種類型, 那麼所有的類型到哪裏看呢 🤔️?

你能夠在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羣」便可😊.

LinDaiDai公衆號二維碼.jpg
LinDaiDai公衆號二維碼.jpg

系列所有目錄請查看此處: 《全網最詳bpmn.js教材》

系列相關推薦:

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

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

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

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

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

《全網最詳bpmn.js教材-編輯、刪除節點篇》

《全網最詳bpmn.js教材-封裝組件篇》

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

《全網最詳bpmn.js教材-properties-panel篇(上)》 ;

相關文章
相關標籤/搜索