✨✨✨目前成都的"小学生"大佬和作者一起开发了 Flowable 流程引擎组件(包含前端设计器与后端流程引擎)。
该组件与 Flowable 流程引擎深度融合,结合实际业务场景和使用方式,对属性编辑面板进行了重新设计,优化了用户体验。 增加了符合业务场景的流程校验与进度预览、引入富文本编辑器与代码编辑器。 结合后端引擎,可直接嵌入系统中使用。
详情请访问:https://www.bpmport.com/products ;
设计器预览:
- 编辑器:designer,
- 预览与模拟:viewer,
- DMN决策设计器:dmn
在开发流图编辑器的过程中,为了深度适配后端 flowable 引擎的功能,经常需要对 bpmn-js 进行扩展,主要分为两个部分:propertie-panel 和 bpmn model。
之前有介绍过,bpmn-js 是由 camunda 团队开发的,用来实现 BPMN 2.0 规则的流程图绘制;结合 bpmn-js-properties-panel 可以完美适配 Camunda 流程引擎。
但是,官方提供的 properties-panel 基本上很难满足国内的使用场景(主要还是 UI 以及交互方式),并且无法适配 flowable 和 activiti。
所以基本上都需要开发人员根据 bpmn-js 提供的 API 来单独实现 properties-panel,而关于 properties-panel 的实现我们在之前的文章中也有讲述过,这里不再赘述。
另外一个问题:bpmn model,则是与 Bpmn.js自定义描述文件说明 中的内容有关。
BPMN Model
关于 bpmn model 的具体概念,很难有一个具体的说法,大致解释就是:通过一个 JSON Schema 来对 BPMN 2.0 规则的 XML 文件内的标签以及属性进行描述。
在 bpmn-js 中,xml 和 JavaScript 之间的联系,是通过 bpmn-moddle 来实现的,而 bpmn-moddle 依赖于 moddle 和 moddle-xml。
其中 moddle 的作用就是 “A utility library for working with meta-model based data structures”,也就是 提供了一种通过 json
文件来定义 XML
元模型的方式,并且实现 XML
字符串与 JavaScript
对象之间的转换。
不过仓库中并没有直接给出这个 json schema 的具体结构,只有通过代码大概梳理如下:
type Schema = {name: stringuri: stringprefix: stringxml?: {typePrefix?: stringtagAlias?: 'lowerCase'}types: ModelType[]enumerations?: EnumType[]associations?: unknown[]
}type EnumType = {name: stringliteralValues: LiteralValue[]
}type LiteralValue = {name: string
}type ModelType = {name: stringisAbstract?: booleansuperClass?: string[]extends?: string[]meta?: {allowedIn?: string[][key: string]: unknown}properties?: ModelProperty[]
}type ModelProperty = {name: stringtype: 'Boolean' | 'String' | 'Integer' | AnotherModelTypeisId?: booleanisAttr?: booleanisBody?: booleanisReference?: booleanisMany?: booleanxml?: {serialize?: stringtypePrefix?: string}default?: string | number | booleanredefines?: string
}
关于 xml 业务逻辑部分的元素和属性声明,格式大概遵循上面这种类型定义。
至于 xml 中关于图形定义(图形类型、位置、大小等)部分,则 ModelProperty
中还会有 isVirtual
、subsettedProperty
等属性,不过这些类型一般不涉及业务部分,对于我们的使用和扩展也没有多少关联,所以不用太过深入的了解。
Schema 与 ModelType 字段说明
在最外层的 Schema
中,主要有四个必要属性:
name
:模型文件名,一般来说只需要描述这个模型文件的用途即可,没有实际作用。prefix
:前缀,用来区别不同元素模型,最常见的就是:bpmn、flowable、activiti、camunda
,当然也可以自定义前缀。uri
:即统一资源标识符(Uniform Resource Identifier),定义该模型文件资源地址。xml
:指定配置xml
的一些格式要求,目前只有tagAlias
和typePrefix
两个属性,内部会比较tagAlias
值是否是lowerCase
来确定是否需要转换为小写驼峰;而typePrefix
则是用于某些特定情况下的类型转换,当中新类型前缀,可选。types
:这里用于定义元素模型的所有元素和属性信息。enumerations
和associations
:这两个部分目前在实际业务中没有具体使用,可选。
在使用过程中,moddle 会解析所有 json schema 文件,生成一个 DescriptorTree
。解析过程中,会校验文件中的 name
和 uri
属性值,如果存在相同值,则会抛出异常并忽略后面解析到的内容。
至于 types 数组中的每个具体定义,参见 ModelType
:
name
:属性/元素对应的类型名称(某些时候很有用~)。isAbstract
:确定这个类型是否可以通过moddle.create
创建一个对象实例(实际上这个属性并没有实际作用,默认都可以通过 moddle.create 创建)。meta
: 元数据信息,一般来说都用来配置allowedIn
,设置该类型允许被添加到哪些类型对象下。superClass
:表示多继承,接受一个类型名称组成的数组,会继承数组内每个类型的所有属性。extends
:表示扩展,接受一个类型名称组成的数组,自身一般不会被直接使用,而是作为配置类型的补充,为配置类型增加新的属性配置。properties
:该类型对应的具体属性定义数组,类型为ModelProperty[]
。
ModelProperty
的说明如下:
name
:属性名称,作为该对象实例的一个属性键名。type
:该属性的类型,一般来说常用Boolean
、String
两个参数来声明基础的属性,也可以用具体的某个ModelType['name']
对应的值,来表示该属性是一个指定类型的对象实例。isId
:标识属性是否可以作为id
属性。isAttr
:标识该属性是否需要作为 xml 标签属性。isBody
:标识该属性是否需要显示在 xml 的标签内部。isMany
:是否是一个数组。isReference
:是否是通过 id 引用一个对象实例,在 xml 中显示为 id,而在 Javascript 运行中体现为对象。redefines
:是否是重定义,指定某个类型声明下的某个具体属性,用来覆盖该指定属性。default
:默认值,一般在Boolean
、String
两个类型的属性中使用。xml
:xml 转换定义,包含serialize
和typePrefix
两个定义,其中serialize
常用的有两个值:xsi:type
和property
;typePrefix
则没有固定值,但一般是使用单个字母。
关于
serialize
和typePrefix
,两者与 xml 的生产和解析有关系,但比较复杂。具体影响后面会更新。
在 ModelProperty
中,部分属性的配置其实是 互斥 的,比如 isAttr
、isBody
和 isMany
等。
现在,我们已经了解了一个 BPMN 的 Json Schema 文件的结构,可以着手扩展元素模型了。
扩展 BPMN20 模型
在 bpmn-js 中,当我们需要在原基础上对元素模型进行扩展的时候,除了需要编写一个 Json Schema 文件之外,还需要在初始化编辑器的时候将该文件内容作为参数传递给 Modeler 构造函数,通过 moddle 进行文件解析。
import Modeler from 'bpmn-js/lib/Modeler'
import FlowableDescriptor from '@/additional/flowable.json'...const modeler = new Modeler({container: '#canvas',moddleExtensions: {flowable: FlowableDescriptor}
})
然后,通过 modeler.importXML
读取具有自定义属性或者元素的 xml 的时候,就可以正常读取了,也可以正常生成新的 xml 字符串。
当然,这些属性也必须要在我们的 flowable.json
文件中有声明。
假设我们现在有这样一个定义:
{"name": "Flowable","uri": "http://flowable.org/bpmn","prefix": "flowable","xml": {"tagAlias": "lowerCase"},types: [{"name": "AssigneeType","superClass": ["Element"],"meta": {"allowedIn": ["bpmn:UserTask"]},"properties": [{"name": "body","type": "String","isBody": true}]}]
}
这其中声明了一个新的类型 AssigneeType
,继承自 Element
。
为什么这里的
Element
没有前缀部分呢?
这其实和底层依赖 moddl-xml 有关,如果说 moddle 是用来实现元模型定义和构造的话,moddle-xml 就是实现 xml 和 JavaScript 对象相互转换的角色。
在 moddle-xml 进行转换的过程中,如果此时继承的类型是 Element
,并且是通过 moddle.create
创建的该类型对应的对象实例,则在转换时会作为一个新的标签插入到目标元素标签中(当然,也不是只有继承自 Element
才会被转换为标签)。
例如此时,我们的 AssigneeType
在创建了一个对应的实例之后,如果正常转换为 xml 的话,则会体现为:<flowable:assigneeType />
。
根据我们配置的 meta.allowedIn
,限制了这个标签只能出现在 <bpmn:userTask></bpmn:userTask>
内部。当然,具体在内部的什么位置,还需要参考 bpmn:UserTask
的具体配置。
那么我们现在去到 bpmn-js
内置的 bpmn.json
中查看一下这个 UserTask
的配置到底是怎么样的呢?
{"name": "BPMN20","uri": "http://www.omg.org/spec/BPMN/20100524/MODEL","prefix": "bpmn","types": [{"name": "BaseElement","isAbstract": true,"properties": [{"name": "id","isAttr": true,"type": "String","isId": true},{"name": "documentation","type": "Documentation","isMany": true},{"name": "extensionDefinitions","type": "ExtensionDefinition","isMany": true,"isReference": true},{"name": "extensionElements","type": "ExtensionElements"}]},{"name": "FlowElement","isAbstract": true,"superClass": ["BaseElement"],"properties": [ ... ]},{"name": "FlowNode","isAbstract": true,"superClass": ["FlowElement"],"properties": [ ... ]},{"name": "InteractionNode","isAbstract": true,"properties": [ ... ]},{"name": "Activity","isAbstract": true,"superClass": ["FlowNode"],"properties": [ ... ]},{"name": "Task","superClass": ["Activity", "InteractionNode"]},{"name": "UserTask","superClass": ["Task"],"properties": [{"name": "renderings","type": "Rendering","isMany": true},{"name": "implementation","isAttr": true,"type": "String"}]},]
}
根据继承关系,我们可以看到 UserTask 一路下来继承了很多个类型,而每个类型都有它自己的属性定义,所以到 UserTask 这里它已经隐式的存在了很多种属性声明。
我们现在需要将 AssigneeType
插入到 UserTask 内部,需要 UserTask 中定义的属性值可以为 Element
的类型,或者某个复杂属性内部可以设置 Element
类型变量的属性值;并且,这个属性定义中不能设置 isReference
,毕竟 Element 内置声明中并没有 Id 属性,而且提取 Id 引用之后,就没有办法转为标签结构了。
然后,在查阅了大半的属性声明之后,我们会发现 BaseElement
中声明的 extensionElements
属性最符合这个场景,extensionElements
属性对应 bpmn:ExtensionElements
类型,具有一个 values
属性可以设置多个 Element
类型的变量。
现在,我们可以通过下面这种方式,将 AssigneeType
插入到 UserTask 内部。
const moddle = modeler.get('moddle')
const modeling = modeler.get('modeling')const assigneeType = moddle.create('flowable:AssigneeType', { body: 'static' })
const extensionElements = moddle.create('bpmn:ExtensionElements', { values: [assigneeType] })modeling.updateProperties(userTaskElement, { extensionElements })
得到的 xml 结构如下:
当然,在使用 bpmn-js 的过程中,除了扩展元模型定义之外,很多时候还需要对原有的属性进行修改,也就是配置 redefines
。
但是在使用 redefines
时,还需要注意默认值的问题。在 bpmn-js(依赖的 bpmn-moddle => moddle-xml)中,如果属性值等于默认值,在 xml 中是不会显示的。
而当我们需要在等于默认值的情况下也显示到 xml 中的话,就需要对原有的属性进行重定义。
属性重定义
这里用一个例子来进行说明。
在 bpmn-js 中,AdHocSubProcess
临时子流程具有一个属性 cancelRemainingInstances
,默认为 true,所以 xml 中会有如下情况:
使用 bpmn-js 解析该流程得到的两个子流程对应 js 对象如下:
此时可以看到虽然在 js 中我们可以获取到 cancelRemainingInstances
的实际值,但当其为 true 时无法显示到 xml 中。
那么这个属性在 Json Schema 中的定义是怎么样的呢?
{"name": "BPMN20""types": [{"name": "AdHocSubProcess","superClass": ["SubProcess"],"properties": [{"name": "cancelRemainingInstances","default": true,"isAttr": true,"type": "Boolean"}]}]
}
省略了部分内容。
其中可以看到 isAttr
为 true
,所以这个属性直接显示在 AdHocSubProcess
标签上,默认值 default
为 true
,所以该属性值为 true 时标签上没有该属性。
当我们需要在 cancelRemainingInstances
为 true 时也将其显示到 xml 上的时候,就需要对这个属性进行重定义了。
这里用项目中使用的 flowable 作为示例。
我们创建一个新的 Json Schema 文件 - flowable.json,添加一个针对其添加一个新的 type 类型。
{"name": "flowable""types": [{"name": "FlowableAdHocSubProcess","extends": ["bpmn:AdHocSubProcess"],"properties": [{"name": "cancelRemainingInstances","isAttr": true,"type": "Boolean","redefines": "bpmn:AdHocSubProcess#cancelRemainingInstances"}]}]
}
这部分在这里表示扩展 bpmn 对应的 Json Schema 文件中的 AdHocSubProcess 类型定义,使用 cancelRemainingInstances 属性覆盖 bpmn:AdHocSubProcess 对应的 cancelRemainingInstances 属性。
为了统一,一般来说属性名和格式都会按照原有的格式来编写。
然后在 bpmn-js 的编辑器中引入这个声明文件。
此时我们的 xml 就会变成:
由于是另外一个文件声明,会带上 flowable 的前缀。
不过,虽然我们现在去掉了默认值,使得这个属性有值时都能显示在 xml 中,但是这也会带来一个问题。
重定义会遇到的问题
在之前的定义中,cancelRemainingInstances
具有默认值配置,所以即使 xml 中没有这个属性,也会将其解析为默认值 true。
而重定义之后,取消了默认值定义,那么此时必须按照 xml 中该属性对应的值来解析,如果不存在该属性,则在 js 中读取该元素 cancelRemainingInstances
属性的值便是 undefined
。
所以此时我们需要处理 在创建 AdHocSubProcess 时如何添加默认值 的问题。
至于设置默认值的问题,有几种解决思路:
- 创建完毕后手动赋值或者调api更新,但是会影响撤销恢复
- 通过 eventBus 在元素创建过程中赋值,这种方式比较完美,类似于
behavior
,但是需要很了解 bpmn-js 的结构和执行逻辑 - 修改 bpmnFactory 模块,可能会对其他元素造成影响,但是还算方便
具体的实现方式,就留给各位小伙伴思考吧~
theme: devui-blue
highlight: darcula
✨✨✨目前成都的"小学生"大佬和作者一起开发了 Flowable 流程引擎组件(包含前端设计器与后端流程引擎)。
该组件与 Flowable 流程引擎深度融合,结合实际业务场景和使用方式,对属性编辑面板进行了重新设计,优化了用户体验。 增加了符合业务场景的流程校验与进度预览、引入富文本编辑器与代码编辑器。 结合后端引擎,可直接嵌入系统中使用。
详情请访问:https://www.bpmport.com/products ;
设计器预览:
- 编辑器:designer,
- 预览与模拟:viewer,
- DMN决策设计器:dmn
在开发流图编辑器的过程中,为了深度适配后端 flowable 引擎的功能,经常需要对 bpmn-js 进行扩展,主要分为两个部分:propertie-panel 和 bpmn model。
之前有介绍过,bpmn-js 是由 camunda 团队开发的,用来实现 BPMN 2.0 规则的流程图绘制;结合 bpmn-js-properties-panel 可以完美适配 Camunda 流程引擎。
但是,官方提供的 properties-panel 基本上很难满足国内的使用场景(主要还是 UI 以及交互方式),并且无法适配 flowable 和 activiti。
所以基本上都需要开发人员根据 bpmn-js 提供的 API 来单独实现 properties-panel,而关于 properties-panel 的实现我们在之前的文章中也有讲述过,这里不再赘述。
另外一个问题:bpmn model,则是与 Bpmn.js自定义描述文件说明 中的内容有关。
BPMN Model
关于 bpmn model 的具体概念,很难有一个具体的说法,大致解释就是:通过一个 JSON Schema 来对 BPMN 2.0 规则的 XML 文件内的标签以及属性进行描述。
在 bpmn-js 中,xml 和 JavaScript 之间的联系,是通过 bpmn-moddle 来实现的,而 bpmn-moddle 依赖于 moddle 和 moddle-xml。
其中 moddle 的作用就是 “A utility library for working with meta-model based data structures”,也就是 提供了一种通过 json
文件来定义 XML
元模型的方式,并且实现 XML
字符串与 JavaScript
对象之间的转换。
不过仓库中并没有直接给出这个 json schema 的具体结构,只有通过代码大概梳理如下:
type Schema = {name: stringuri: stringprefix: stringxml?: {typePrefix?: stringtagAlias?: 'lowerCase'}types: ModelType[]enumerations?: EnumType[]associations?: unknown[]
}type EnumType = {name: stringliteralValues: LiteralValue[]
}type LiteralValue = {name: string
}type ModelType = {name: stringisAbstract?: booleansuperClass?: string[]extends?: string[]meta?: {allowedIn?: string[][key: string]: unknown}properties?: ModelProperty[]
}type ModelProperty = {name: stringtype: 'Boolean' | 'String' | 'Integer' | AnotherModelTypeisId?: booleanisAttr?: booleanisBody?: booleanisReference?: booleanisMany?: booleanxml?: {serialize?: stringtypePrefix?: string}default?: string | number | booleanredefines?: string
}
关于 xml 业务逻辑部分的元素和属性声明,格式大概遵循上面这种类型定义。
至于 xml 中关于图形定义(图形类型、位置、大小等)部分,则 ModelProperty
中还会有 isVirtual
、subsettedProperty
等属性,不过这些类型一般不涉及业务部分,对于我们的使用和扩展也没有多少关联,所以不用太过深入的了解。
Schema 与 ModelType 字段说明
在最外层的 Schema
中,主要有四个必要属性:
name
:模型文件名,一般来说只需要描述这个模型文件的用途即可,没有实际作用。prefix
:前缀,用来区别不同元素模型,最常见的就是:bpmn、flowable、activiti、camunda
,当然也可以自定义前缀。uri
:即统一资源标识符(Uniform Resource Identifier),定义该模型文件资源地址。xml
:指定配置xml
的一些格式要求,目前只有tagAlias
和typePrefix
两个属性,内部会比较tagAlias
值是否是lowerCase
来确定是否需要转换为小写驼峰;而typePrefix
则是用于某些特定情况下的类型转换,当中新类型前缀,可选。types
:这里用于定义元素模型的所有元素和属性信息。enumerations
和associations
:这两个部分目前在实际业务中没有具体使用,可选。
在使用过程中,moddle 会解析所有 json schema 文件,生成一个 DescriptorTree
。解析过程中,会校验文件中的 name
和 uri
属性值,如果存在相同值,则会抛出异常并忽略后面解析到的内容。
至于 types 数组中的每个具体定义,参见 ModelType
:
name
:属性/元素对应的类型名称(某些时候很有用~)。isAbstract
:确定这个类型是否可以通过moddle.create
创建一个对象实例(实际上这个属性并没有实际作用,默认都可以通过 moddle.create 创建)。meta
: 元数据信息,一般来说都用来配置allowedIn
,设置该类型允许被添加到哪些类型对象下。superClass
:表示多继承,接受一个类型名称组成的数组,会继承数组内每个类型的所有属性。extends
:表示扩展,接受一个类型名称组成的数组,自身一般不会被直接使用,而是作为配置类型的补充,为配置类型增加新的属性配置。properties
:该类型对应的具体属性定义数组,类型为ModelProperty[]
。
ModelProperty
的说明如下:
name
:属性名称,作为该对象实例的一个属性键名。type
:该属性的类型,一般来说常用Boolean
、String
两个参数来声明基础的属性,也可以用具体的某个ModelType['name']
对应的值,来表示该属性是一个指定类型的对象实例。isId
:标识属性是否可以作为id
属性。isAttr
:标识该属性是否需要作为 xml 标签属性。isBody
:标识该属性是否需要显示在 xml 的标签内部。isMany
:是否是一个数组。isReference
:是否是通过 id 引用一个对象实例,在 xml 中显示为 id,而在 Javascript 运行中体现为对象。redefines
:是否是重定义,指定某个类型声明下的某个具体属性,用来覆盖该指定属性。default
:默认值,一般在Boolean
、String
两个类型的属性中使用。xml
:xml 转换定义,包含serialize
和typePrefix
两个定义,其中serialize
常用的有两个值:xsi:type
和property
;typePrefix
则没有固定值,但一般是使用单个字母。
关于
serialize
和typePrefix
,两者与 xml 的生产和解析有关系,但比较复杂。具体影响后面会更新。
在 ModelProperty
中,部分属性的配置其实是 互斥 的,比如 isAttr
、isBody
和 isMany
等。
现在,我们已经了解了一个 BPMN 的 Json Schema 文件的结构,可以着手扩展元素模型了。
扩展 BPMN20 模型
在 bpmn-js 中,当我们需要在原基础上对元素模型进行扩展的时候,除了需要编写一个 Json Schema 文件之外,还需要在初始化编辑器的时候将该文件内容作为参数传递给 Modeler 构造函数,通过 moddle 进行文件解析。
import Modeler from 'bpmn-js/lib/Modeler'
import FlowableDescriptor from '@/additional/flowable.json'...const modeler = new Modeler({container: '#canvas',moddleExtensions: {flowable: FlowableDescriptor}
})
然后,通过 modeler.importXML
读取具有自定义属性或者元素的 xml 的时候,就可以正常读取了,也可以正常生成新的 xml 字符串。
当然,这些属性也必须要在我们的 flowable.json
文件中有声明。
假设我们现在有这样一个定义:
{"name": "Flowable","uri": "http://flowable.org/bpmn","prefix": "flowable","xml": {"tagAlias": "lowerCase"},types: [{"name": "AssigneeType","superClass": ["Element"],"meta": {"allowedIn": ["bpmn:UserTask"]},"properties": [{"name": "body","type": "String","isBody": true}]}]
}
这其中声明了一个新的类型 AssigneeType
,继承自 Element
。
为什么这里的
Element
没有前缀部分呢?
这其实和底层依赖 moddl-xml 有关,如果说 moddle 是用来实现元模型定义和构造的话,moddle-xml 就是实现 xml 和 JavaScript 对象相互转换的角色。
在 moddle-xml 进行转换的过程中,如果此时继承的类型是 Element
,并且是通过 moddle.create
创建的该类型对应的对象实例,则在转换时会作为一个新的标签插入到目标元素标签中(当然,也不是只有继承自 Element
才会被转换为标签)。
例如此时,我们的 AssigneeType
在创建了一个对应的实例之后,如果正常转换为 xml 的话,则会体现为:<flowable:assigneeType />
。
根据我们配置的 meta.allowedIn
,限制了这个标签只能出现在 <bpmn:userTask></bpmn:userTask>
内部。当然,具体在内部的什么位置,还需要参考 bpmn:UserTask
的具体配置。
那么我们现在去到 bpmn-js
内置的 bpmn.json
中查看一下这个 UserTask
的配置到底是怎么样的呢?
{"name": "BPMN20","uri": "http://www.omg.org/spec/BPMN/20100524/MODEL","prefix": "bpmn","types": [{"name": "BaseElement","isAbstract": true,"properties": [{"name": "id","isAttr": true,"type": "String","isId": true},{"name": "documentation","type": "Documentation","isMany": true},{"name": "extensionDefinitions","type": "ExtensionDefinition","isMany": true,"isReference": true},{"name": "extensionElements","type": "ExtensionElements"}]},{"name": "FlowElement","isAbstract": true,"superClass": ["BaseElement"],"properties": [ ... ]},{"name": "FlowNode","isAbstract": true,"superClass": ["FlowElement"],"properties": [ ... ]},{"name": "InteractionNode","isAbstract": true,"properties": [ ... ]},{"name": "Activity","isAbstract": true,"superClass": ["FlowNode"],"properties": [ ... ]},{"name": "Task","superClass": ["Activity", "InteractionNode"]},{"name": "UserTask","superClass": ["Task"],"properties": [{"name": "renderings","type": "Rendering","isMany": true},{"name": "implementation","isAttr": true,"type": "String"}]},]
}
根据继承关系,我们可以看到 UserTask 一路下来继承了很多个类型,而每个类型都有它自己的属性定义,所以到 UserTask 这里它已经隐式的存在了很多种属性声明。
我们现在需要将 AssigneeType
插入到 UserTask 内部,需要 UserTask 中定义的属性值可以为 Element
的类型,或者某个复杂属性内部可以设置 Element
类型变量的属性值;并且,这个属性定义中不能设置 isReference
,毕竟 Element 内置声明中并没有 Id 属性,而且提取 Id 引用之后,就没有办法转为标签结构了。
然后,在查阅了大半的属性声明之后,我们会发现 BaseElement
中声明的 extensionElements
属性最符合这个场景,extensionElements
属性对应 bpmn:ExtensionElements
类型,具有一个 values
属性可以设置多个 Element
类型的变量。
现在,我们可以通过下面这种方式,将 AssigneeType
插入到 UserTask 内部。
const moddle = modeler.get('moddle')
const modeling = modeler.get('modeling')const assigneeType = moddle.create('flowable:AssigneeType', { body: 'static' })
const extensionElements = moddle.create('bpmn:ExtensionElements', { values: [assigneeType] })modeling.updateProperties(userTaskElement, { extensionElements })
得到的 xml 结构如下:
当然,在使用 bpmn-js 的过程中,除了扩展元模型定义之外,很多时候还需要对原有的属性进行修改,也就是配置 redefines
。
但是在使用 redefines
时,还需要注意默认值的问题。在 bpmn-js(依赖的 bpmn-moddle => moddle-xml)中,如果属性值等于默认值,在 xml 中是不会显示的。
而当我们需要在等于默认值的情况下也显示到 xml 中的话,就需要对原有的属性进行重定义。
属性重定义
这里用一个例子来进行说明。
在 bpmn-js 中,AdHocSubProcess
临时子流程具有一个属性 cancelRemainingInstances
,默认为 true,所以 xml 中会有如下情况:
使用 bpmn-js 解析该流程得到的两个子流程对应 js 对象如下:
此时可以看到虽然在 js 中我们可以获取到 cancelRemainingInstances
的实际值,但当其为 true 时无法显示到 xml 中。
那么这个属性在 Json Schema 中的定义是怎么样的呢?
{"name": "BPMN20""types": [{"name": "AdHocSubProcess","superClass": ["SubProcess"],"properties": [{"name": "cancelRemainingInstances","default": true,"isAttr": true,"type": "Boolean"}]}]
}
省略了部分内容。
其中可以看到 isAttr
为 true
,所以这个属性直接显示在 AdHocSubProcess
标签上,默认值 default
为 true
,所以该属性值为 true 时标签上没有该属性。
当我们需要在 cancelRemainingInstances
为 true 时也将其显示到 xml 上的时候,就需要对这个属性进行重定义了。
这里用项目中使用的 flowable 作为示例。
我们创建一个新的 Json Schema 文件 - flowable.json,添加一个针对其添加一个新的 type 类型。
{"name": "flowable""types": [{"name": "FlowableAdHocSubProcess","extends": ["bpmn:AdHocSubProcess"],"properties": [{"name": "cancelRemainingInstances","isAttr": true,"type": "Boolean","redefines": "bpmn:AdHocSubProcess#cancelRemainingInstances"}]}]
}
这部分在这里表示扩展 bpmn 对应的 Json Schema 文件中的 AdHocSubProcess 类型定义,使用 cancelRemainingInstances 属性覆盖 bpmn:AdHocSubProcess 对应的 cancelRemainingInstances 属性。
为了统一,一般来说属性名和格式都会按照原有的格式来编写。
然后在 bpmn-js 的编辑器中引入这个声明文件。
此时我们的 xml 就会变成:
由于是另外一个文件声明,会带上 flowable 的前缀。
不过,虽然我们现在去掉了默认值,使得这个属性有值时都能显示在 xml 中,但是这也会带来一个问题。
重定义会遇到的问题
在之前的定义中,cancelRemainingInstances
具有默认值配置,所以即使 xml 中没有这个属性,也会将其解析为默认值 true。
而重定义之后,取消了默认值定义,那么此时必须按照 xml 中该属性对应的值来解析,如果不存在该属性,则在 js 中读取该元素 cancelRemainingInstances
属性的值便是 undefined
。
所以此时我们需要处理 在创建 AdHocSubProcess 时如何添加默认值 的问题。
至于设置默认值的问题,有几种解决思路:
- 创建完毕后手动赋值或者调api更新,但是会影响撤销恢复
- 通过 eventBus 在元素创建过程中赋值,这种方式比较完美,类似于
behavior
,但是需要很了解 bpmn-js 的结构和执行逻辑 - 修改 bpmnFactory 模块,可能会对其他元素造成影响,但是还算方便
具体的实现方式,就留给各位小伙伴思考吧~