bpmn-js中实现shape的内置属性、节点的默认配置

bpmn-js中使用elementfactory模块来构建一个元素的结构,其构建构成和元素属性的组成可参考:聊一聊bpmn-js中的elementFactory模块。构建元素的属性会自动帮我们生成一个对应类型的shapeId,其余属性均为空,需要我们后续手动添加。

ElementFactory.prototype.create = function(type, attrs) {
attrs
= assign({}, attrs || {});
if (!attrs.id) {
attrs
.id = type + '_' + (this._uid++); // 自动生成id
}
return create(type, attrs);
};

为了方便用户操作和隐藏某些固定配置信息,现在我希望在用户创建的时候就将某些配置信息固定配置进入对应的shape,以节省流程编辑器制作时间,也防止某些敏感配置出现不可预知的错误配置。通过对bpmn-js的结构的了解和分析,针对不同使用常见我总结了两种方式对齐进行配置的注入操作。

期望达到的效果

对于配置注入的最终结果有如下描述:

  • 单个组件创建会自动配置一个默认名称

  • bpmn:UserTask配置默认的任务监听器

  • 配置内置扩展属性

通过EventBus实现

bpmn-js使用内置的eventbus事件总线的方式进行事件的监听和传递,我们可以借助事件总线中的内置事件选择合适的时机对元素进行属性配置操作。这种方式的好处就是不需要额外去修改palettecontextPad插件即可实现,侵入性小,操作比较独立。

通过eventbus实现功能前,先来了解下几个比较重要的内置事件:

  • element.changed:选中元素发生改变【所有类型元素】

  • shape.addedshape类型新增元素事件,当首次导入的时候,每个shape的新增也都会触发

  • shape.removedshape类型图形移除事件

  • import.parse.start: xml开始导入事件

  • import.donexml导入结束事件

如下是bpmn-js中这个几个事件的执行顺序图示:


bpmn-js中实现shape的内置属性、节点的默认配置


需要注意的几个点:

  • shape.added的时机有两个地方:一个是导入加载已有数据,一个是新增shape的时候

  • shape的属性写入必须要在element.changed之后操作才可生效,shape.added监听后不可直接对element进行操作

几个写入的方法

根据上述了解,我们需要实现如下几个功能:

  • 写入shape属性

  • 写入shape扩展属性

  • 写入shape执行任务监听器:此功能为flowable扩展功能,想要此功能实现,还需提前添加flowablemoddle适配声明插件,详见:谈一谈bpmn-js中的moddleExtensions

如下为我封装的几个写入操作的函数,代码如下:


// 创建一个元素id
export function uuid(
length
= 8,
chars
: any = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
) {
let result
= ''
const charsString = chars
for (let i = length; i > 0; --i)
result
+= charsString[Math.floor(Math.random() * charsString.length)]
return result
}
/**
* 更新bpmn-js中元素的属性更新操作
* @param modeler bpmn-js中的操作对象modeler
* @param element 当前待操作的元素
* @param key 需要更新的key
* @param value 更新后的value
*/

export const updateProperty = (modeler: any, element: any, key: string, value: any) => {
const modeling = modeler.get('modeling') // 依据didi设计的插件获取方式

const attrObj = Object.create(null)
attrObj
[key] = value

if (element && element[key] === attrObj[key]) {
console
.log('属性值未发生改变,请忽略:', element[key])
return
}
if (modeling && element) {
if (key === 'id') {
// 更新属性
modeling
.updateProperties(element, {
id
: value,
di
: { id: `${value}_di` },
})
}
else {
modeling
.updateProperties(element, attrObj)
}
}
}

/**
* 添加扩展属性
* @param modeler bpmn-js中的操作对象modeler
* @param element 当前待操作的元素
* @param key 需要更新的key
* @param value 更新后的value
*/

export const addExtensionProperty = (modeler: any, element: any, key: string, value: any) => {
const modeling = modeler.get('modeling') // 依据didi设计的插件获取方式
const elementRegistry = modeler.get('elementRegistry')
const moddle = modeler.get('moddle')
const targetElement = elementRegistry.get(element.id)
if (!targetElement)
return
// bpmn:properties
const otherPropertis: any = []
const properties = targetElement.businessObject?.extensionElements?.values.filter((ex: any) => {
const type = ex.$type.split(':')[1] || ''
if (type !== 'Properties')
otherPropertis
.push(ex)
return type === 'Properties'
}) ?? []
const values: any[] = properties.reduce((last: any[], current: any) => last.concat(current.values), [])

const current = values.find((item: any) => item.name === key) // 存在当前key

if (current) {
// 当前key已存在,需要进行更新
modeling
.updateModdleProperties(targetElement, current, { name: key, value })
}
else {
// 当前key不存在,需要创建一个
const newPropertyObject = moddle.create('flowable:Property', { name: key, value })
const propertiesObject = moddle.create(('flowable:Properties'), {
values
: values.concat([newPropertyObject]),
})

const extensionElements = moddle.create('bpmn:ExtensionElements', {
values
: otherPropertis.concat([propertiesObject]),
})
modeling
.updateProperties(targetElement, {
extensionElements
,
})
}
}

const createScriptObject = (moddle: any, options: any) => {
const { scriptType, scriptFormat, value, resource } = options
const scriptConfig: any = { scriptFormat }
if (scriptType === 'inlineScript')
scriptConfig
.value = value
else scriptConfig.resource = resource

return moddle.create('flowable:Script', scriptConfig)
}

/**
* 添加任务监听器
* @param modeler bpmn-js中的操作对象modeler
* @param element 当前待操作的元素
* @param model
*/

export const addTaskListenerProperty = (modeler: any, element: any, model: any) => {
const modeling = modeler.get('modeling') // 依据didi设计的插件获取方式
const elementRegistry = modeler.get('elementRegistry')
const moddle = modeler.get('moddle')
const targetElement = elementRegistry.get(element.id)
if (!targetElement)
return
const otherExtensionList: any[] = []
const properties = targetElement.businessObject?.extensionElements?.values?.filter(
(ex: any) => {
if (ex.$type !== 'flowable:TaskListener')
otherExtensionList
.push(ex)
return ex.$type === 'flowable:TaskListener'
},
) ?? []

const listenerObj = Object.create(null)
listenerObj
.event = model.event
switch (model.listenerType) {
case 'scriptListener':
listenerObj
.script = createScriptObject(moddle, model)
break

case 'expressionListener':
listenerObj
.expression = model.expression
break

case 'delegateExpressionListener':
listenerObj
.delegateExpression = model.delegateExpression
break

default:
listenerObj
.class = model.class
}

if (model.event === 'timeout' && !!model.eventDefinitionType) {
// 超时定时器
const timeDefinition = moddle.create('bpmn:FormalExpression', {
body
: model.eventTimeDefinitions,
})
const TimerEventDefinition = moddle.create('bpmn:TimerEventDefinition', {
id
: `TimerEventDefinition_${uuid(8)}`,
[`time${model.eventDefinitionType.replace(/^S/, (s: string) => s.toUpperCase())}`]:
timeDefinition
,
})
listenerObj
.eventDefinitions = [TimerEventDefinition]
}

const listenerObject = moddle.create('flowable:TaskListener', listenerObj)

properties
.push(listenerObject)

const extensionElements = moddle.create('bpmn:ExtensionElements', {
values
: otherExtensionList.concat(properties),
})
modeling
.updateProperties(targetElement, {
extensionElements
,
})
}

实现

东风具备,接下来就是如何实现扩展属性等的创建插入了,原理就是参考上述的执行事件顺序,通过记录状态在shape.added中添加element.changed方法【原理是元素创建后会自定聚焦当前元素,会主动发起一次element.changed事件】,去除监听,以防止重复占有元素聚焦导致逻辑死循环。伪代码实现如下:

const importDone = ref<boolean>(false) // 导入状态

....
// 确认导入是否完成
modeler
.on('import.parse.start', () => {
importDone
.value = false
})

modeler
.on('import.done', (e: any) => {
importDone
.value = true
})

modeler
.on('shape.added', (event: any) => {
if (importDone.value) {
// 编辑过程中新增元素
const listener = ({ element }: any) => {
if (element.id === event?.element.id) {
modeler
.get('eventBus').off('element.changed', listener)
updateProperty
(modeler, element, 'name', '测试节点名称')
addExtensionProperty
(modeler, element, 'test', 'property') // 添加扩展属性
addTaskListenerProperty
(modeler, element, {
event: 'create',
listenerType
: 'classListener',
class: 'cn.enjoytoday.bpmnClassLisener',
}) // 添加默认执行任务
}
}

modeler
.get('eventBus').on('element.changed', listener)
}
})
...

结果

使用palettecontextpad追加两种方式测试新增节点,获取xml文件如下:

bpmn-js中实现shape的内置属性、节点的默认配置

结果成功!

自定义Palette和ContextPad

若是深度定制可以通过在shape创建的时候配置shape属性实现,在开始添加内置属性之前,我们先来了解下shapebpmn-js中直接创建的场景,以及内置属性创建的具体格式。

创建场景

bpmn-js提供的建模器来说内置创建元素模块主要分为两个地方:PalettecontextPad,其具体代码部分如下:

1、PaletteProvider.js

bpmn-js中实现shape的内置属性、节点的默认配置

2、ContextPadProvider.js

bpmn-js中实现shape的内置属性、节点的默认配置

内置对象属性

上传我们可以发现其实这两个地方的实现是一样的,都是通过elementFactory.createShape来创建一个shape对象,然后通过create.start进行创建。由于createShape方法只是生成了一个id,所以为了创建内置属性配置,我们就需要自己新增属性,在开始实现之前,我们首先需要了解到shape元素的描述属性都是在shape.businessObject对象下的,如下是一个shape元素的数据结构

bpmn-js中实现shape的内置属性、节点的默认配置

由于palettecontextpad的实现本质一致,我这里就在自定义的palettepalette自定义参考:聊一聊bpmn-js中的Palette)中实现内置属性挂载。其实现代码:

// PaletteProvider.js 由于创建ModdleElement对象需要用到moddle模块,需要在inject中添加moddle
function createListener(event) {
const shape = elementFactory.createShape(assign({ type }, options))

if (options) {
!shape.businessObject.di && (shape.businessObject.di = {})
shape
.businessObject.di.isExpanded = options.isExpanded
}

// 这里开始是插入内置属性代码
shape
.businessObject.name = '测试节点名称'

const testProp = moddle.create('flowable:Property', { name: 'test', value: '123' })
const task = moddle.create('flowable:TaskListener', { event: 'creat', class: 'cn.enjoytoday.bpmnClassLisener' })
const a = moddle.create('flowable:Properties', {
values
: [testProp],
})
const extensionElements = moddle.create('bpmn:ExtensionElements', {
values
: [a, task],
})

shape
.businessObject.extensionElements = extensionElements
//将属性插入到extensionElements中


create
.start(event, shape)
}

结果

测试发现,可正常实现内置属性插入,得出xml文件如下:

bpmn-js中实现shape的内置属性、节点的默认配置

总结

两种方式均可实现内置属性节点的配置插入,第一种方式通过适配式的方式实现,尽可能少的影响建模其的独立性和完整性,后一种方式比较直接,一次性完成创建配置,减少shape的绘制次数,但代码侵入性较高,不利于不同场景的适配共用。具体可根据需求选择使用何种方式进行实现。


原文始发于微信公众号(胖蔡话前端):bpmn-js中实现shape的内置属性、节点的默认配置

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/239206.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!