最近有思考工作流相关的事情,绘制bpmn图的工具认可度比较高的就是bpmn.js了,是一个基于node.js的流程图绘制框架。初始的框架只实现了基本的可视化,想在xml进行客制化操作的话需要拓展,简单记录下几个需求的实现过程。
修改基础
在bpmnjs官方提供的Properties-panel拓展上进行修改和拓展。Properties-panel提供了流程绘制时的右侧拓展面板,可以在可视化界面中对xml文件进行修改。
Properties-panel的GitHub是 https://github.com/bpmn-io/bpmn-js-examples/tree/master/properties-panel-extension。
原始的panel如图,只能设个name和id。
实现拓展的目标
简单的几个需求:
- 在
ServiceTask
标签中增加delegate expression
属性,使其能设置对应的task实现类。 - 在
ExclusiveGateway
标签中增加子标签<activiti:executionListener>
,标签中内容为状态监听实现类,标签唯一。 - 在
ExclusiveGateway
标签的extensionElements
标签中增加子标签<activiti:executionListener>
子标签,可增加复数子标签。子标签中包含event属性和delegateExpression属性可进行设置,并实现name属性的自动生成。event属性默认设为start。
ServiceTask属性增加
新增属性json设置
在app/descriptors
中增加对新增属性的描述activiti.json
,这个json中定义的新属性后续会被bpmnjs读取并使用。
{
"name": "activiti",
"prefix": "activiti",
"uri": "http://activiti",
"xml": {
"tagAlias": "lowerCase"
},
"associations": [],
"types": [
{
"name": "ActivitiServiceTask",
"extends": [
"bpmn:ServiceTask"
],
"properties": [
{
"name": "delegateExpression",
"isAttr": true,
"type": "String"
}
]
},
主要是在普通的ServiceTask基础上进行拓展,增加了一个attribute并命名为delegateExpression
。需要注意的是,因为之前设置了prefix前缀,所以最后的属性其实会变为activiti:delegateExpression
。
DelegateExpresion部件的设置
简单来说,对panel的拓展,就是将想要的组件加进去,并对组件进行事件的修改,以关联到xml文件的修改。因此,对delegateExpression先进行组件的撰写,放在provider/activiti/parts
文件夹中。
DelegateExpression.js
import { html } from 'htm/preact';
import { TextFieldEntry, isTextFieldEntryEdited } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
export default function(element) {
// 返回delegateExpression输入框设置
return [
{
id: 'delegateExpression',
element,
// 设置事件
component: delegateExpression,
isEdited: isTextFieldEntryEdited
}
];
}
// 属性的增加
function delegateExpression(props) {
const { element, id } = props;
const modeling = useService('modeling');
const translate = useService('translate');
const debounce = useService('debounceInput');
// 返回的信息,用来获取对应值生成xml
const getValue = () => {
return element.businessObject.delegateExpression || '';
}
// 设置xml写入的信息
const setValue = value => {
return modeling.updateProperties(element, {
delegateExpression: value
});
}
return html`<${TextFieldEntry}
id=${ id }
element=${ element }
description=${ translate('set delegate expression') }
label=${ translate('设置自动任务task') }
getValue=${ getValue }
setValue=${ setValue }
debounce=${ debounce }
/>`
}
主要是对component
对应方法的修改,返回一个TextFieldEntry
,也就是bpmnjs预设的输入文本框。其中关键的是getValue
和setValue
。getValue是在可视化界面中打开时进行的操作,也就是获取当前对象ServiceTask标签中的delegateExpression属性并显示。因为在json文件中定义过这个属性,所以这里可以直接调用。setValue主要进行xml相关的操作,关键是updateProperties
方法,封装了对xml进行修改的操作。
编写provider部分
在provider/activiti文件夹下,我们创建一个ActivitiPropertiesProvider.js
文件,用于向可视化界面的右侧panel面板中增加部件。
ActivitiPropertiesProvider.js
import DelegateExpression from './parts/DelegateExpression';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { ListGroup } from '@bpmn-io/properties-panel';
首先引入之前写完的delegateExpression组件。
ActivitiPropertiesProvider.$inject = [ 'propertiesPanel', 'injector', 'translate' ];
// 构建右侧面板中的delegate expression
function createDelegateExpression(element, translate) {
const delegateExpressionGroup = {
id: 'DelegateExpression',
label: translate('对应实现类表达式设置'),
entries: DelegateExpression(element)
};
return delegateExpressionGroup
}
首先将panel项目中的几个组件进行注入,方便使用。createDelegateExpression
方法中对delegateExpression组件进行了组装,主要是设置了在图形界面上表示的label,entries中调用了上文中写的delegateExpression方法,返回textField对象。
// 主方法,对右侧栏进行扩展
export default function ActivitiPropertiesProvider(propertiesPanel, injector, translate) {
// 组中增加对应的项目
this.getGroups = function(element) {
return function(groups) {
// 自动节点,增加自动任务的task表达式设置
if(is(element, 'bpmn:ServiceTask')){
groups.push(createDelegateExpression(element, translate));
}
return groups;
}
};
propertiesPanel.registerProvider(LOW_PRIORITY, this);
最后在主方法进行判断,在图形界面中碰到ServiceTask之后,向右侧面板中增加delegateExpression设置用的文本框。最后进行register即可,LOW_PRIORITY是一个常量,看官方文档说是把增加的group放到最下面,实际用起来好像设成什么都没区别。
在provider/activiti下增加index.js用于provider的导出:
import ActivitiPropertiesProvider from './ActivitiPropertiesProvider';
export default {
__init__: [ 'ActivitiPropertiesProvider' ],
ActivitiPropertiesProvider: [ 'type', ActivitiPropertiesProvider ]
};
导入extension
在使用时,需要导入写好的拓展配置。demo中在app文件夹下写了index.js
用于导入。
import activitiModdleDescriptor from './descriptors/activiti.json';
import activitiPropertiesProviderModule from './provider/activiti';
var bpmnModeler = new BpmnModeler({
container: '#js-canvas',
propertiesPanel: {
parent: '#js-properties-panel'
},
additionalModules: [
BpmnPropertiesPanelModule,
BpmnPropertiesProviderModule,
activitiPropertiesProviderModule
],
moddleExtensions: {
activiti: activitiModdleDescriptor
}
});
将json的拓展属性描述放入moddleExtensions
中,将provider放入additionMoudles
中即可。
ExclusiveGateway子标签增加
大部分修改和ServiceTask中的差不多,贴几个关键代码吧。
activiti.json
{
"name": "ActivitiExclusiveGateway",
"extends": [
"bpmn:ExclusiveGateway"
],
"properties": [
{
"name": "executionListener",
"isAttr": false,
"type": "String"
}
]
},
主要是将isAttr设为false,这样就能变成子标签了。
ActitiviPropertiesProvider.js中增加:
// 网关增加listener属性
if(is(element, 'bpmn:ExclusiveGateway')){
groups.push(createExecutionListener(element, translate));
}
createExecutionListener方法
// 构建右侧面板中的execution listener
function createExecutionListener(element, translate){
const executionListenerGroup = {
id: 'ExecutionListener',
label: translate('execution listener'),
entries: ExecutionListener(element)
};
return executionListenerGroup;
}
其他的跟着上一节来就行,完成设置。
前两个需求的效果展示
启动方法,在文件目录中使用:
npm install
npm start
即可进行项目启动,默认打开app下的index.html页面。
成功实现panel面板的拓展。
成功实现xml的修改。
ExclusiveGateway的ExtensionElements成员增加
篇幅关系,这部分挪到另一篇中讲,文章地址bpmnjs Properties-panel拓展(ExtensionElements篇)。
总结
简单记录了下利用bpmnjs的properties panel来实现对xml文件拓展的可视化界面修改方法,主要是简单增加属性或增加子标签。js接触的不是很多,node.js基本不会,所以这个项目主要还是在本地自己用用。
整体拓展流程可以总结为设计组件与事件,注册组件到group,主js中调用拓展三步走,还是比较直观的。整体代码已上传Github https://github.com/huiluczP/huiluczp-activiti-properties-panel-extension,有兴趣可以看看。