接上文bpmnjs Properties-panel拓展(属性设置篇),继续记录下第三个拓展需求的实现。
需求简述
在ExclusiveGateway
标签的extensionElements
标签中增加子标签<activiti:executionListener>
子标签,可增加复数子标签。子标签中包含event
属性和delegateExpression
属性可进行设置,并实现name属性的自动生成。event属性默认设为start。
那么最终的结构可表示为:
- exclusiveGateway
- extensionElements
- activiti:exectionListener (event, name, delegateExpression)
json属性设置
在activiti.json
中增加要拓展的属性
{
"name": "executionListener",
"superClass": [ "Element" ],
"isMany": true,
"properties": [
{
"name": "event",
"isAttr":true,
"type": "String"
},{
"name": "delegateExpression",
"isAttr": true,
"type": "String"
}
]
}
补充executionListener的具体定义,主要的是superClass
,设为Element
,这样会被归到extensionElements里。
ExtensionExecutionListener组件
用来构建单条的executionListener。
ExtensionExecutionListener.js
import { TextFieldEntry } from '@bpmn-io/properties-panel';
import { useService } from 'bpmn-js-properties-panel';
// 构建list中的一层,包括event设置和delegateExpression设置
export default function ExtensionExecutionListener(props) {
const {
idPrefix,
element,
executionListener
} = props;
const entries = [
{
// idPrefix是外部传进来的element id,所以增加一个后缀来进行区分
id: idPrefix + '-event',
component: event,
idPrefix,
executionListener
},
{
id: idPrefix + '-delegateExpression',
component: delegateExpression,
idPrefix,
executionListener
}
];
return entries;
}
// 设置event属性
function event(props) {
// 要注意的是executionListener,需要它来进行属性的修改
const {
idPrefix,
element,
executionListener
} = props;
const commandStack = useService('commandStack');
const translate = useService('translate');
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: executionListener,
properties: {
event: value
}
});
};
const getValue = (executionListener) => {
return executionListener.event;
};
// 也可使用html拼接
return TextFieldEntry({
element: executionListener,
id: idPrefix + '-event',
label: translate('event'),
getValue,
setValue,
debounce
});
}
// 设置delegateExpression属性
function delegateExpression(props) {
const {
idPrefix,
element,
executionListener
} = props;
const commandStack = useService('commandStack');
const translate = useService('translate');
const debounce = useService('debounceInput');
const setValue = (value) => {
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: executionListener,
properties: {
delegateExpression: value
}
});
};
// exectionListener获取属性
const getValue = (executionListener) => {
return executionListener.delegateExpression;
};
// 也可使用html拼接
return TextFieldEntry({
element: executionListener,
id: idPrefix + '-delegateExpression',
label: translate('delegateExpression'),
getValue,
setValue,
debounce
});
}
主要是构建了包含event和delegateExpression的entries。需要注意的是,和前文bpmnjs Properties-panel拓展(属性设置篇)中不同,这里使用了commandStack
命令栈去直接执行update
,这种方法后面也会用到,好处是能将数个指令存到array中一起执行,比较方便写,不过错误提示会变成一坨,可能是我用的不对吧。
ExtensionExecutionListenerGroup
需要在ExtensionElements中塞进多个ExecutionListener,因此需要构建其组成的list。
import {
getBusinessObject
} from 'bpmn-js/lib/util/ModelUtil';
import { without } from 'min-dash';
import ExtensionExecutionListener from './ExtensionExecutionListener';
import Ids from 'ids';
function nextId(prefix) {
const ids = new Ids([32, 32, 1]);
return ids.nextPrefixed(prefix);
}
function createElement(elementType, properties, parent, factory) {
const element = factory.create(elementType, properties);
if (parent) {
element.$parent = parent;
}
return element;
}
首先是几个工具方法,getId
随机构建不重复的id信息,createElement
方法利用bpmnFactory
来构建bpmnElement,是一种bpmnjs定义的结构体,用于后续插入整体的xml中。
// 获取element的extensionElment下的所有ExecutionListener
function getExtensionExecutionListeners(element) {
const businessObject = getBusinessObject(element);
// 不存在就算了
if (!businessObject.extensionElements) {
return null;
}
// 存在则使用filter找出所有的activiti:exectionListener
return businessObject.extensionElements.values.filter(function (e) {
return e.$instanceOf("activiti:executionListener");
});
}
工具方法,找executionListener。executionListener是在extensionElement下的,所以从那里利用filter
找,返回是一个array或空。
// 关键方法,构建listener list
export default function ExtensionExecutionListenerGroup({ element, injector }) {
// 防空
const executionListeners = getExtensionExecutionListeners(element) || [];
console.log(executionListeners);
const bpmnFactory = injector.get('bpmnFactory'),
commandStack = injector.get('commandStack');
// 对list中的每个item进行构建
const items = executionListeners.map((executionListener, index) => {
// 按顺序给个id
const id = element.id + '-executionListener-' + index;
// 构建item
return {
id,
label: executionListener.get('event') + '---' + executionListener.get('delegateExpression') || '',
entries: ExtensionExecutionListener({
idPrefix: id,
element,
executionListener
}),
autoFocusEntry: id + '-el',
remove: removeFactory({ commandStack, element, executionListener })
};
});
return {
items,
add: addFactory({ element, bpmnFactory, commandStack })
};
}
主要是进行组件的构建。需要注意的是设置remove
方法和add
方法,分别是在可视化界面中进行组件增加和删除时调用的方法。
// 去除item时执行的方法,先获取extensionElement,之后进行without处理,最后更新
function removeFactory({ commandStack, element, executionListener }) {
return function (event) {
event.stopPropagation();
const executionListeners = getExtensionExecutionListeners(element);
if (!executionListeners) {
return;
}
const businessObject = getBusinessObject(element);
// 利用without将当前item对应的信息剔除
console.log(executionListeners);
const executionListenersAfter = without(executionListeners, executionListener);
console.log(executionListenersAfter);
// 更新剔除后的信息
commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: businessObject.get('extensionElements'),
properties: {
values: executionListenersAfter
}
});
};
}
remove
中主要工作就是先找到所有executionListener组成的array,之后通过without
方法去除要删除的对象,最后进行update。
// 增加item时执行的方法
function addFactory({ element, bpmnFactory, commandStack }) {
return function (event) {
event.stopPropagation();
// 存放处理命令,最后使用commandStack执行
const commands = [];
const businessObject = getBusinessObject(element);
let extensionElements = businessObject.get('extensionElements');
// extensionElements是bpmn自带的属性,不存在则先创建
if (!extensionElements) {
extensionElements = createElement(
'bpmn:ExtensionElements',
{ values: [] },
businessObject,
bpmnFactory
);
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: businessObject,
properties: { extensionElements }
}
});
}
// 构建exectionListener
const newExecutionListener = createElement('activiti:executionListener', {
name: nextId('ExecutionListener_'),
event: 'start', // 这边其实可以改成下拉框
delegateExpression: ''
}, extensionElements, bpmnFactory);
// 增加至extensionElements
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: extensionElements,
properties: {
// 使新增的显示在下面
values: [newExecutionListener, ...extensionElements.get('values')]
}
}
});
commandStack.execute('properties-panel.multi-command-executor', commands);
};
}
增加item主要分为三步,新增extensionElements,构建executionListener并给出随机id,update。
注册到可视化面板
在ActivitiPropertiesProvider.js
中将组件进行注册
// 网关增加extensionElement:ExecutionListener
if(is(element, 'bpmn:ExclusiveGateway')){
groups.push(createExtensionExclusiveGateway(element, injector, translate));
}
// 构建extensionElement下的exectionListener
function createExtensionExclusiveGateway(element, injector, translate){
// 构建group list
const elGroup = {
id: 'ExtensionExectionListener',
label: translate('对应监听实现类设置'),
component: ListGroup,
...ExtensionExecutionListenerGroup({ element, injector })
};
return elGroup;
}
实现效果
list增删成员
xml成功修改
总结
实现了增加ExtensionElements中标签成员的需求,其实实现逻辑还是比较清晰的,构建组件,组件list,注册三步走。不过写的时候还是踩了不少坑,调了蛮久,还得多练啊。项目已上传至Github https://github.com/huiluczP/huiluczp-activiti-properties-panel-extension,感兴趣可以看一下。