全網最詳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的內容後, 應該對屬性有了一個大概的認識吧.git

這一章節讓咱們來講說properties-panel 😄...github

其實在前面的《全網最詳bpmn.js教材-基礎篇》中已經提到了怎樣使用properties-panel, 不過那裏只是簡單的教了你們如何引用而沒有細說, 如今就讓我來詳細爲你們講解一下它具體的使用方法。web

經過這一章節的閱讀你能夠學習到:npm

  • Properties-panel的基本使用
  • 擴展使用 Properties-panel

Properties-panel的基本使用

properties-panel本質上是bpmn.js的一個擴展, 它實現了BPMN 2.0建模器,使你能夠經過屬性面板編輯與執行相關的屬性。

官方的一個截圖:

1. 安裝properties-panel

在以前的文章中有不少內容沒有介紹清楚, 在這一章中我會仔細的介紹.

首先是安裝上.

若是你想要使用它的話, 得本身安裝一下:

$ npm install --save bpmn-js-properties-panel
複製代碼

一樣的記得在項目中引入樣式:

import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右邊工具欄樣式
複製代碼

使用上, 得在html代碼中提供一個標籤做爲盛放它的容器:

<div id="js-properties-panel" class="panel"></div>
複製代碼

以後, 在構建BpmnModeler的時候添加上它:

// 這裏引入的是右側屬性欄這個框
import propertiesPanelModule from 'bpmn-js-properties-panel'
// 而這個引入的是右側屬性欄裏的內容
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'

const bpmnModeler = new BpmnModeler({
	//添加控制板
  propertiesPanel: {
        parent: '#js-properties-panel'
  },
  additionalModules: [
  	propertiesPanelModule,
  	propertiesProviderModule
  ]
})
複製代碼

在以前的文章中我沒有弄清楚propertiesPanelModulepropertiesProviderModule的做用, 致使將左側工具欄和右側屬性的引用方式寫錯了, 如今已經在<全網最詳bpmn.js教材-基礎篇>中更正了...抱歉...

2. 安裝camunda-bpmn-moddle

還有一點, 若是你想使用Camunda BPM來執行相關屬性的話, 也得安裝一個叫camunda-bpmn-moddle的擴展:

$ npm install --save camunda-bpmn-moddle
複製代碼

將其添加到項目中:

// 右側屬性欄
import propertiesPanelModule from 'bpmn-js-properties-panel'
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
 // 一個描述的json
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'

const bpmnModeler = new BpmnModeler({
	//添加控制板
  propertiesPanel: {
  	parent: '#js-properties-panel'
  },
  additionalModules: [
  	propertiesPanelModule,
  	propertiesProviderModule
  ],
  moddleExtensions: {
    //若是要在屬性面板中維護camunda:XXX屬性,則須要此 
    camunda: camundaModdleDescriptor
  }
})
複製代碼

(Camunda BPM是一個用於工做流執行引擎和工做流自動化的解決方案, 在這裏就不展開說了)

camunda-bpmn-moddle的做用就是告訴使用者camunda:XXX擴展屬性。

說了這個咱也聽不懂啊, 來講點具體的吧, 好比你已經安裝並已經在項目上使用了properties-panel以後, 打開頁面, 隨便選擇一個節點(就拿開始節點來講吧), 會出現四個選項卡(tab)能讓你修改屬性, 若是你沒有安裝camunda並引用camundaModdleDescriptor的話, 使用後面三個功能, 控制檯就會報錯了:

它會告訴你unknown type <camunda:FormData>...

由於其實你查看camunda-bpmn-moddle/resources/camunda的源碼就會發現, 這其實就是一個json文件, 裏面存放的就是對各個屬性的描述. 咱們在後面自定義properties-panel的時候也會須要編寫這樣的一個json文件, 待會你就知道了.

3. 實際使用效果

OK...讓咱們來實際使用看看它們有什麼效果.

爲了方便查看, 我給bpmnModeler綁定一個commandStack.changed事件, 在圖形每次改變的時候將最新的xml打印出來.

(關於事件綁定的部分能夠看<全網最詳bpmn.js教材-事件篇>)

以後仍是點擊開始節點, 並修改一些屬性. 結果發現你修改的屬性居然同步更新到了xml上面:

Good Body! 你是否是想到了什麼?!

沒錯! 和上一篇文章的updateProperties方法是否是很像呢, 都是可以更新屬性到xml上.

擴展使用Properties-panel

palette, contextPad等自定義方式同樣, Properties-panel也能夠在默認的基礎上進行修改, 它容許你加上一些自定義的屬性.

不過官方把它叫作Properties Panel Extension, 好像更專業一些...不過無所謂了, 你知道是那個意思就好了.

官方這裏也提供了一個例子: properties-panel-extension

我其實也是跟着官方的這個例子來探索它是怎麼使用的.

首先讓咱們來明確一點, 還記得咱們在使用原版properties-panel的時候, 引入了兩個東西嗎?

// 這裏引入的是右側屬性欄這個框
import propertiesPanelModule from 'bpmn-js-properties-panel'
// 而這個引入的是右側屬性欄裏的內容
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'

additionalModules: [
  propertiesPanelModule,
  propertiesProviderModule
]
複製代碼

我研究了一下, 若是你不引入第一個只引入第二個的話, 屬性欄就出不來了.

而若是你只引入第一個不引入第二個的話, 就會報錯...

我理解一下大概是這樣意思:

  • 第一個 propertiesPanelModule表示的是屬性欄這個框, 就是告訴別人這裏要有個屬性欄;
  • 第二個 propertiesProviderModule表示的是屬性欄裏的內容, 也就是點擊不一樣的 element該顯示什麼內容.

看到這, 你是否是有了點思路呢? 嘻嘻😁...

既然這樣的話, 咱們只須要重寫propertiesProviderModule就能夠了, 不要引入官方提供的(也就是從bpmn-js-properties-panel/lib/provider/camunda引入的), 而是自定義一個propertiesProviderModule來顯示本身想要的內容.

1. 前期準備

properties-panel的內容可能有點多, 我就另外建立了一個項目來作案例分析.

項目仍是用vue來編寫, 不過其實你只要有點基礎都能看得懂.

先讓咱們來看看要實現的效果:

  • 點擊開始節點的時候, 右側的屬性欄中有 General和權限兩個選項卡(tab);
  • 權限這個選項卡中有一個組, 名爲 編輯權限;
  • 編輯權限下會有一個屬性, 名爲 標題, 它是一個輸入框;
  • 修改該開始節點的信息, 能將屬性關聯到 xml

讓咱們在components文件夾下建立一個properties-panel-extension文件夾, 這裏用來放咱們要自定義的屬性內容.

而後在properties-panel-extension下再新建一個descriptorsprovider文件夾.

  • descriptors是用來放一些描述的 json文件
  • provider放你要自定義的選項卡(tab)

因爲General是它本來就有的一個選項卡, 因此咱們能夠不用管它, 如今咱們想要自定義的是一個名叫「權限」的選項卡, 因此我在provider文件夾下又建立了一個authority文件夾, 裏面用來放咱們選項卡的內容...

以後一頓操做, 讓目錄變成了這樣:

AuthorityPropertiesProvider.js這個文件就是來編寫權限這個選項卡的, 它是咱們須要編寫的主要文件.

parts這個文件夾就是來放各個組下的子元素, 好比這裏的「標題」, 我給它取名爲TitleProps.

2. provider返回值介紹

若是你看到上面那麼多的文件感受眼花繚亂的話, 請沒必要慌張😂, 這是正常的反應...

因此爲了後面更好的講解, 我決定先來介紹一下provider的返回值與頁面的結構是如何對應上的.

經過上面👆的圖, 咱們能夠看出來:

  1. 每一個 provider下都會有一個 tabs數組(一個 tab就是一個選項卡)
  2. 每一個 tab下都會有一個 groups數組(一個 group就是一個組)
  3. 每一個 group下都會有一堆 props, 它們多是輸入框, 也多是下拉框

OK...如今是否是好理解多了😄...

因此咱們只須要在AuthorityPropertiesProvider.js中返回一個這樣的結構就能夠了:

/*-選項卡 | -組 | -屬性*/
return [
    { // 選項卡
        id: 'general',
        groups: [] // 組
    },
    { // 選項卡
        id: 'authority',
        groups: [
            { // 組
                id: 'edit-authority', // 組id
                entries: [
                    { // 單個props
                        id: 'title',
                        description : '權限的標題',
                        label : '標題',
                        modelProperty : 'title'
                    }
                ]
            }
        ]
    }
]
複製代碼

3. 編寫AuthorityPropertiesProvider.js代碼

編寫的順序我打算從上往下一層一層的講.

因此先來看看AuthorityPropertiesProvider.js整體是要返回什麼.

// AuthorityPropertiesProvider.js

import inherits from 'inherits';
// 引入自帶的PropertiesActivator, 由於咱們要用到它來處理eventBus
import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator';

export default function AuthorityPropertiesProvider( eventBus, bpmnFactory, canvas, // 這裏是要用到什麼就引入什麼 elementRegistry, translate ) {
    PropertiesActivator.call(this, eventBus);
    
    this.getTabs = function (element) {
        var generalTab = {};
        var authorityTab = {};
        return [
            generalTab,
            authorityTab
        ];
    }
}

inherits(AuthorityPropertiesProvider, PropertiesActivator);
複製代碼

這樣看, 結構是否是也很清晰呢? 😊

咱們其實就是要重寫裏面的getTabs方法, 返回咱們須要的tab.

每一個tab都有固定的屬性:

var authorityTab = {
        id: 'authority',
        label: '權限',
        groups: createAuthorityTabGroups(element)
    };
複製代碼

你必須得準守以上命名規則來寫哈😣...

編寫createAuthorityTabGroups函數代碼

在肯定了tab以後, 咱們須要告訴它裏面有哪些組, 這時候就能夠建立一個createAuthorityTabGroups函數來返回想要的組.

// AuthorityPropertiesProvider.js

import TitleProps from './parts/TitleProps';

function createAuthorityTabGroups(element) {
    var editAuthorityGroup = {
        id: 'edit-authority',
        label: '編輯權限',
        entries: [] // 屬性集合
    }
    // 每一個屬性都有本身的props方法
    TitleProps(editAuthorityGroup, element);
    // OtherProps1(editAuthorityGroup, element);
    // OtherProps2(editAuthorityGroup, element);
    
    return [
        editAuthorityGroup
    ];
}
複製代碼

好比上面👆我就返回了一個編輯權限的組.

而各個屬性是放到組的entries字段下的...

咿呀, 這裏怎麼沒看到給entries數組添加屬性呢?

可是下面好像有一個TitleProps呀, 這個是幹嗎的🤔?

看着有點像用來添加屬性的...

編寫TitleProps.js代碼

是的, 因爲屬性可能會被多處用到, 因此我將它單獨提了出來, 放到了parts這個文件夾下, 後面就能夠往裏面不停的加屬性了.

這個屬性的方法有點特別, 它接收兩個參數:

  • 一個組
  • 當前 element

由於同一個屬性可能存在於不一樣的組裏, 因此能夠傳入一個組.

另外可能要經過元素的類型來作各類判斷, 因此能夠傳入當前元素.

// /parts/TitleProps.js
import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';

import { is } from 'bpmn-js/lib/util/ModelUtil';

export default function(group, element) {
  if (is(element, 'bpmn:StartEvent')) { // 能夠在這裏作類型判斷
    group.entries.push(entryFactory.textField({
      id : 'title',
      description : '權限的標題',
      label : '標題',
      modelProperty : 'title'
    }));
  }
}
複製代碼

啊😺, 原來entries是在每個Props裏添加屬性的啊🙈...

push方法裏, 你得告訴它是要添加一個什麼類型的Props.

主要就是經過entryFactory, 例如這裏就是返回一個text類型的輸入框.

有時候你想要的不只僅是輸入框怎麼辦🙈?

不要緊, entryFactory自己爲你提供了不少類型.

Ctrl + 左鍵查看entryFactory的源碼, 你能夠發現有不少類型:

OK...

至此, 咱們的自定義authorityTab權限選項卡就寫完了 😊!

你若是想添加其它的選項卡用上面👆的方式就能夠了...

編寫generalTab代碼

上面👆的權限選項卡是咱們自定義的一些內容, 若是你想要使用官方提供的一些tab和屬性怎麼辦呢?

generalTab就爲你演示了該如何作...

首先一樣的, generalTab須要長成這樣:

var generalTab = {
    id: 'general',
    label: 'General',
    groups: createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate)
};
複製代碼

咱們看到createGeneralTabGroups好像傳遞了不少參數進去, 那是由於咱們要在裏面用到它們, 而這些參數在構造AuthorityPropertiesProvider函數的時候就引入進來的...

來看看createGeneralTabGroups是如何編寫的:

// AuthorityPropertiesProvider.js

import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps';
import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps';
import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps';
import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps';
import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps';
import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps';

function createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) {

    var generalGroup = {
        id: 'general',
        label: 'General',
        entries: []
    };
    idProps(generalGroup, element, translate);
    nameProps(generalGroup, element, bpmnFactory, canvas, translate);
    processProps(generalGroup, element, translate);

    var detailsGroup = {
        id: 'details',
        label: 'Details',
        entries: []
    };
    linkProps(detailsGroup, element, translate);
    eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate);

    var documentationGroup = {
        id: 'documentation',
        label: 'Documentation',
        entries: []
    };

    documentationProps(documentationGroup, element, bpmnFactory, translate);

    return [
        generalGroup,
        detailsGroup,
        documentationGroup
    ];
}
複製代碼

general中, 導出了三個組, 而每一個組中的Props都是bpmn-js-properties-panel/lib/provider/bpmn/parts這個文件夾中拿的...

一樣的, 你查找它的源碼, 也能發現不少其它的Props, 你須要什麼, 直接取來用就能夠了[狗頭].

AuthorityPropertiesProvider.js完整代碼

額, 要不仍是貼下完整的代碼?

其實也很少, 91行:

import inherits from 'inherits';

import PropertiesActivator from 'bpmn-js-properties-panel/lib/PropertiesActivator';


import idProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/IdProps';
import nameProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/NameProps';
import processProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/ProcessProps';
import linkProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/LinkProps';
import eventProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/EventProps';
import documentationProps from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/DocumentationProps';

import TitleProps from './parts/TitleProps';

function createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate) {

    var generalGroup = {
        id: 'general',
        label: 'General',
        entries: []
    };
    idProps(generalGroup, element, translate);
    nameProps(generalGroup, element, bpmnFactory, canvas, translate);
    processProps(generalGroup, element, translate);

    var detailsGroup = {
        id: 'details',
        label: 'Details',
        entries: []
    };
    linkProps(detailsGroup, element, translate);
    eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate);

    var documentationGroup = {
        id: 'documentation',
        label: 'Documentation',
        entries: []
    };

    documentationProps(documentationGroup, element, bpmnFactory, translate);

    return [
        generalGroup,
        detailsGroup,
        documentationGroup
    ];
}

function createAuthorityTabGroups(element) {
    var editAuthorityGroup = {
        id: 'edit-authority',
        label: '編輯權限',
        entries: []
    }

    // 每一個屬性都有本身的props方法
    TitleProps(editAuthorityGroup, element);
    // OtherProps1(editAuthorityGroup, element);
    // OtherProps2(editAuthorityGroup, element);

    return [
        editAuthorityGroup
    ];
}

export default function AuthorityPropertiesProvider( eventBus, bpmnFactory, canvas, // 這裏是要用到什麼就引入什麼 elementRegistry, translate ) {
    PropertiesActivator.call(this, eventBus);

    this.getTabs = function(element) {
        var generalTab = {
            id: 'general',
            label: 'General',
            groups: createGeneralTabGroups(element, bpmnFactory, canvas, elementRegistry, translate)
        };

        var authorityTab = {
            id: 'authority',
            label: '權限',
            groups: createAuthorityTabGroups(element)
        };
        return [
            generalTab,
            authorityTab
        ];
    }
}

inherits(AuthorityPropertiesProvider, PropertiesActivator);

複製代碼

通過咱們的拆分, 感受異常簡單有木有 😊 !

(沒錯, 霖呆呆就是這麼一個簡單善良如白紙通常的男子😳...)

4. 編寫authority.json代碼

OK...其實到了這裏就接近尾聲了, 可是其實還有很是關鍵的一步要作...

剛剛咱們自定義了一個叫作權限的選項卡, 還有一個叫title的屬性, 而且還指定了只有StartEvent中出現, 那麼此時咱們還得在一個叫authority.json的文件中作一些說明.

(之因此取名爲authority.json, 是由於我添加的選項卡叫權限, 這個命名隨便你本身)

它長成這樣:

{
    "name": "Authority",
    "prefix": "authority",
    "uri": "http://authority",
    "xml": {
      "tagAlias": "lowerCase"
    },
    "associations": [],
    "types": [
      {
        "name": "LinDaiDaiStartEvent",
        "extends": [
          "bpmn:StartEvent"
        ],
        "properties": [
          {
            "name": "title",
            "isAttr": true,
            "type": "String"
          }
        ]
      }
    ]
  }
複製代碼

在這個描述文件中, 咱們定義了一個新類型LinDaiDaiStartEvent, 該類型擴展了該類型bpmn:StartEvent並向其添加「title」屬性做爲屬性。

️: 有必要在描述符中定義要擴展的元素。若是但願該屬性對全部bpmn元素均有效,則能夠擴展bpmn:BaseElement

例如🌰這樣:

...
{
  "name": "LinDaiDaiStartEvent",
  "extends": [
    "bpmn:BaseElement"
  ],
  ...
}
複製代碼

5. 導出並使用AuthorityPropertiesProvider

通過一輪翻雲覆雨(C + V)的操做, 終於將大頭給寫完了...

下面讓咱們來看看怎麼用它...

/provider/authority文件夾下建立一個index.js用於導出:

import AuthorityPropertiesProvider from './AuthorityPropertiesProvider';

export default {
  __init__: [ 'propertiesProvider' ],
  propertiesProvider: [ 'type', AuthorityPropertiesProvider ]
};
複製代碼

看着很眼熟啊, 哈哈😄... 和contextPad什麼的好像...

用於演示, 我在項目中建立了一個properties-panel-extension.vue, 並在其中引用上bpmn.js和咱們的剛剛編寫好的authority.

<template>
  <div class="containers" ref="content">
    <div class="canvas" ref="canvas"></div>
    <div id="js-properties-panel" class="panel"></div>
  </div>
</template>
<script> // 原有的 properties-panel 這個框 import propertiesPanelModule from 'bpmn-js-properties-panel' // 自定義的 properties-panel內容 import propertiesProviderModule from './properties-panel-extension/provider/authority'; // 引入描述文件 import authorityModdleDescriptor from './properties-panel-extension/descriptors/authority' ... additionalModules: [ // 右邊的工具欄(固定引入) propertiesPanelModule, // 自定義右邊工做欄的內容 propertiesProviderModule ], moddleExtensions: { // camunda: camundaModdleDescriptor, authority: authorityModdleDescriptor } ... </script>
複製代碼

看到這裏, 相信你對properties-panel又有了一個新的認識...

恭喜你🎉🎉🎉

霖呆呆非常欣慰...

後語

上面👆教材案例的代碼地址: LinDaiDai/bpmn-vue-properties-panel

關於properties-panel要講的內容有點多, 因此我將其分爲了兩篇來寫.

還有幾天過年了🧨...霖呆呆有個小小的願望, 就是在年前能破200的粉絲...

卑微博主在線懇求關注...哈哈哈😂

(看着好心酸)

最後, 若是你也對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篇》

相關文章
相關標籤/搜索