# 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 }
});
}
```
畫面結果

## 參考資料
* [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)