# bpmn-js study notes 記錄整合 bpmn-js 到 Angular 的筆記 ## API ## 與 angular 整合 ### 安裝套件 ```shell= npm install bpmn-js --save npm install bpmn-js-properties-panel --save npm install camunda-bpmn-moddle --save ``` ### 在 style.scss 引用 css ```typescript= @import '~bpmn-js/dist/assets/diagram-js'; @import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css'; @import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css'; ``` ### 在 component html 加上畫布和屬性的元件 ```htmlmixed= <div class="modeler"> <div #canvas class="flow-container"></div> <div #properties class="properties-panel" ></div> </div> ``` 此#的參數要跟 @ViewChild 相同 ### 在 component ts 配置 #### import ```typescript= import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; //引入 bpmn-js 套件 import BpmnModeler from 'bpmn-js/lib/Modeler'; //引入屬性欄 import propertiesPanelModule from 'bpmn-js-properties-panel'; //引入 camuda 的屬性 import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'; //載入 camunda 各項設定的定義檔 import * as propertiesDescriptor from 'camunda-bpmn-moddle/resources/camunda.json'; ``` #### 利用 ViewChild 對應到 html 上的 div ```typescript= @ViewChild('canvas', { static: true }) private elCanvas: ElementRef; @ViewChild('properties', { static: true }) private elPropPanel: ElementRef; ``` #### 宣告變數 ```typescript= private bpmnJS: Modeler; //空白的 bpmn-js 畫布的路徑 private readonly newDiagram = 'assets/bpmn/initialDiagram.bpmn'; ``` #### ngOnInit ```typescript= this.bpmnJS = new Modeler({ container: this.elCanvas.nativeElement, propertiesPanel: { parent: this.elPropPanel.nativeElement }, additionalModules: [ propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { camunda: propertiesDescriptor['default'] }, keyboard: { bindTo: document } //可用鍵盤來操控元件 }); ``` ## 客製 Panel (初步研究) - import 客製 panel 需要的 class ```typescript= import EntryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory'; import PaletteProvider from 'bpmn-js/lib/features/palette/PaletteProvider'; ``` - 基於現有的圖片,追加一個新的圖 (基於 bpmn:Task,對應的配置會參考 bpmn:Task) ```typescript= export class CustomPaletteProvider { private elementFactory; static $inject = [ 'bpmnFactory', 'create', 'elementFactory', 'palette', 'translate' ]; constructor(bpmnFactory, create, elementFactory, palette, translate) { palette.registerProvider(this); this.elementFactory = elementFactory; } getPaletteEntries(element) { function createTask(event) { return function(event) { const shape = this.elementFactory.createShape({ type: 'bpmn:Task' }); console.log(shape) // 只在拖动或者点击时触发 this.create.start(event, shape); } } return { 'create.lindaidai-task': { group: 'model', className: 'icon-custom lindaidai-task', title: '创建一个类型为 lindaidai-task 的任务节点', action: { dragstart: () => createTask, click: () => createTask } } } } } ``` - ## 客製 Properties Panel (初步研究) src 下新增 properties-panel-extension/descriptors 資料夾 descriptors 下新增 authority.json ```json= { "name": "Authority", "prefix": "test", "uri": "http://authority", "xml": { "tagAlias": "lowerCase" }, "associations": [], "types": [ { "name": "TestStartEvent", "extends": [ "bpmn:StartEvent" ], "properties": [ { "name": "title", "isAttr": true, "type": "String", "default": "a" } ] } ] } ``` properties-panel-extension 下新增 provider/authority 資料夾 authority 下新增 AuthorityPropertiesProvider.js ```javascript= 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: translate('General'), entries: [] }; idProps(generalGroup, element, translate); nameProps(generalGroup, element, bpmnFactory, canvas, translate); processProps(generalGroup, element, translate); var detailsGroup = { id: 'details', label: translate('Details'), entries: [] }; linkProps(detailsGroup, element, translate); eventProps(detailsGroup, element, bpmnFactory, elementRegistry, translate); var documentationGroup = { id: 'documentation', label: translate('Documentation'), entries: [] }; documentationProps(documentationGroup, element, bpmnFactory, translate); return [ generalGroup, detailsGroup, documentationGroup ]; } function createTestTabGroups(element, translate) { var testGroup = { id: 'test-tab', label: '測試tab', entries: [] } // 每个属性都有自己的props方法 TitleProps(testGroup, element, translate); return [ testGroup ]; } // 这里是要用到什么就引入什么 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 testTab = { id: 'testTab', label: '測試tab', groups: createTestTabGroups(element, translate) }; return [ generalTab, testTab ]; } } inherits(AuthorityPropertiesProvider, PropertiesActivator); ``` 新增 index.ts ```javascript= import AuthorityPropertiesProvider from './AuthorityPropertiesProvider'; export default { __init__: [ 'propertiesProvider' ], propertiesProvider: [ 'type', AuthorityPropertiesProvider ] }; ``` authority 下新增 parts 資料夾 新增 TitleProps.js ```javascript= import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory'; import { is } from 'bpmn-js/lib/util/ModelUtil'; export default function(group, element, translate) { if (is(element, 'bpmn:StartEvent') || is(element, 'bpmn:UserTask')) { // 可以在这里做类型判断 group.entries.push(entryFactory.textField(translate,{ id: 'title', description: '权限的标题', label: '标题', modelProperty: 'title' })); } } ``` 在 component ts 改引用客製的 mo ```typescript= import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import * as Modeler from 'bpmn-js/dist/bpmn-modeler.production.min.js'; import { DiagramService } from '../service/diagram.service'; 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.json'; @Component({ selector: 'app-bpmndemo', templateUrl: './bpmndemo.component.html', styleUrls: ['./bpmndemo.component.scss'] }) export class BpmndemoComponent implements OnInit { @ViewChild("canvas", { static: true }) elCanvas: ElementRef @ViewChild("properties", { static: true }) panel: ElementRef private readonly newDiagram = 'assets/bpmn/initialDiagram.bpmn'; bpmnJS: Modeler; selectedElements: any; element: any; constructor(private diagramService: DiagramService) { } ngOnInit(): void { this.bpmnJS = new Modeler({ container: this.elCanvas.nativeElement, propertiesPanel: { parent: this.panel.nativeElement }, additionalModules: [ propertiesPanelModule, propertiesProviderModule ], moddleExtensions: { magic: authorityModdleDescriptor }, keyboard: { bindTo: document } }); } ``` 畫面結果 ![](https://i.imgur.com/xoGlPqE.png) ## 參考資料 * [BPMN-JS 與 Angular 集成系列文章](https://www.twblogs.net/u/5b8c368d2b7177188331591e) * [ngx-bpmn-modeler](https://github.com/d3v0ps/ngx-bpmn-modeler) * [bpmn-js 詳細教材](https://juejin.im/post/6844904017567416328)