bpmn-js 扩展元素模型

bpmn-js 扩展元素模型这里用一个例子来进行说明

大家好,欢迎来到IT知识分享网。

✨✨✨目前成都的”小学生”大佬和作者一起开发了 Flowable 流程引擎组件(包含前端设计器与后端流程引擎)。

该组件与 Flowable 流程引擎深度融合,结合实际业务场景和使用方式,对属性编辑面板进行了重新设计,优化了用户体验。 增加了符合业务场景的流程校验与进度预览、引入富文本编辑器与代码编辑器。 结合后端引擎,可直接嵌入系统中使用。

详情请访问:https://www.bpmport.com/products ;

设计器预览:

  1. 编辑器:designer,
  2. 预览与模拟:viewer,
  3. DMN决策设计器:dmn

image.png

在开发流图编辑器的过程中,为了深度适配后端 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: string uri: string prefix: string xml?: { 
    typePrefix?: string tagAlias?: 'lowerCase' } types: ModelType[] enumerations?: EnumType[] associations?: unknown[] } type EnumType = { 
    name: string literalValues: LiteralValue[] } type LiteralValue = { 
    name: string } type ModelType = { 
    name: string isAbstract?: boolean superClass?: string[] extends?: string[] meta?: { 
    allowedIn?: string[] [key: string]: unknown } properties?: ModelProperty[] } type ModelProperty = { 
    name: string type: 'Boolean' | 'String' | 'Integer' | AnotherModelType isId?: boolean isAttr?: boolean isBody?: boolean isReference?: boolean isMany?: boolean xml?: { 
    serialize?: string typePrefix?: string } default?: string | number | boolean redefines?: string } 

关于 xml 业务逻辑部分的元素和属性声明,格式大概遵循上面这种类型定义。

至于 xml 中关于图形定义(图形类型、位置、大小等)部分,则 ModelProperty 中还会有 isVirtualsubsettedProperty 等属性,不过这些类型一般不涉及业务部分,对于我们的使用和扩展也没有多少关联,所以不用太过深入的了解。

Schema 与 ModelType 字段说明

在最外层的 Schema 中,主要有四个必要属性:

  • name:模型文件名,一般来说只需要描述这个模型文件的用途即可,没有实际作用。
  • prefix:前缀,用来区别不同元素模型,最常见的就是:bpmn、flowable、activiti、camunda,当然也可以自定义前缀。
  • uri:即统一资源标识符(Uniform Resource Identifier),定义该模型文件资源地址。
  • xml:指定配置 xml 的一些格式要求,目前只有 tagAliastypePrefix 两个属性,内部会比较 tagAlias 值是否是 lowerCase 来确定是否需要转换为小写驼峰;而 typePrefix 则是用于某些特定情况下的类型转换,当中新类型前缀,可选。
  • types:这里用于定义元素模型的所有元素和属性信息。
  • enumerationsassociations:这两个部分目前在实际业务中没有具体使用,可选。

在使用过程中,moddle 会解析所有 json schema 文件,生成一个 DescriptorTree。解析过程中,会校验文件中的 nameuri 属性值,如果存在相同值,则会抛出异常并忽略后面解析到的内容

至于 types 数组中的每个具体定义,参见 ModelType

  • name:属性/元素对应的类型名称(某些时候很有用~)。
  • isAbstract:确定这个类型是否可以通过 moddle.create 创建一个对象实例(实际上这个属性并没有实际作用,默认都可以通过 moddle.create 创建)。
  • meta: 元数据信息,一般来说都用来配置 allowedIn,设置该类型允许被添加到哪些类型对象下。
  • superClass:表示多继承,接受一个类型名称组成的数组,会继承数组内每个类型的所有属性。
  • extends:表示扩展,接受一个类型名称组成的数组,自身一般不会被直接使用,而是作为配置类型的补充,为配置类型增加新的属性配置。
  • properties:该类型对应的具体属性定义数组,类型为 ModelProperty[]

ModelProperty 的说明如下:

  • name:属性名称,作为该对象实例的一个属性键名。
  • type:该属性的类型,一般来说常用 BooleanString 两个参数来声明基础的属性,也可以用具体的某个 ModelType['name'] 对应的值,来表示该属性是一个指定类型的对象实例。
  • isId:标识属性是否可以作为 id 属性。
  • isAttr:标识该属性是否需要作为 xml 标签属性。
  • isBody:标识该属性是否需要显示在 xml 的标签内部。
  • isMany:是否是一个数组。
  • isReference:是否是通过 id 引用一个对象实例,在 xml 中显示为 id,而在 Javascript 运行中体现为对象。
  • redefines:是否是重定义,指定某个类型声明下的某个具体属性,用来覆盖该指定属性。
  • default:默认值,一般在 BooleanString 两个类型的属性中使用。
  • xml:xml 转换定义,包含 serializetypePrefix 两个定义,其中 serialize 常用的有两个值:xsi:typepropertytypePrefix 则没有固定值,但一般是使用单个字母。

关于 serializetypePrefix,两者与 xml 的生产和解析有关系,但比较复杂。具体影响后面会更新。

ModelProperty 中,部分属性的配置其实是 互斥 的,比如 isAttrisBodyisMany 等。

现在,我们已经了解了一个 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//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 结构如下:

image.png

当然,在使用 bpmn-js 的过程中,除了扩展元模型定义之外,很多时候还需要对原有的属性进行修改,也就是配置 redefines

但是在使用 redefines 时,还需要注意默认值的问题。在 bpmn-js(依赖的 bpmn-moddle => moddle-xml)中,如果属性值等于默认值,在 xml 中是不会显示的。

而当我们需要在等于默认值的情况下也显示到 xml 中的话,就需要对原有的属性进行重定义。

属性重定义

这里用一个例子来进行说明。

在 bpmn-js 中,AdHocSubProcess 临时子流程具有一个属性 cancelRemainingInstances,默认为 true,所以 xml 中会有如下情况:

image.png

使用 bpmn-js 解析该流程得到的两个子流程对应 js 对象如下:

image.png

此时可以看到虽然在 js 中我们可以获取到 cancelRemainingInstances 的实际值,但当其为 true 时无法显示到 xml 中。

那么这个属性在 Json Schema 中的定义是怎么样的呢?

{ 
    "name": "BPMN20" "types": [ { 
    "name": "AdHocSubProcess", "superClass": ["SubProcess"], "properties": [ { 
    "name": "cancelRemainingInstances", "default": true, "isAttr": true, "type": "Boolean" } ] } ] } 

省略了部分内容。

其中可以看到 isAttrtrue,所以这个属性直接显示在 AdHocSubProcess 标签上,默认值 defaulttrue,所以该属性值为 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 就会变成:

image.png

由于是另外一个文件声明,会带上 flowable 的前缀。

不过,虽然我们现在去掉了默认值,使得这个属性有值时都能显示在 xml 中,但是这也会带来一个问题。

重定义会遇到的问题

在之前的定义中,cancelRemainingInstances 具有默认值配置,所以即使 xml 中没有这个属性,也会将其解析为默认值 true。

而重定义之后,取消了默认值定义,那么此时必须按照 xml 中该属性对应的值来解析,如果不存在该属性,则在 js 中读取该元素 cancelRemainingInstances 属性的值便是 undefined

所以此时我们需要处理 在创建 AdHocSubProcess 时如何添加默认值 的问题。

至于设置默认值的问题,有几种解决思路:

  1. 创建完毕后手动赋值或者调api更新,但是会影响撤销恢复
  2. 通过 eventBus 在元素创建过程中赋值,这种方式比较完美,类似于 behavior,但是需要很了解 bpmn-js 的结构和执行逻辑
  3. 修改 bpmnFactory 模块,可能会对其他元素造成影响,但是还算方便

具体的实现方式,就留给各位小伙伴思考吧~


theme: devui-blue
highlight: darcula

✨✨✨目前成都的”小学生”大佬和作者一起开发了 Flowable 流程引擎组件(包含前端设计器与后端流程引擎)。

该组件与 Flowable 流程引擎深度融合,结合实际业务场景和使用方式,对属性编辑面板进行了重新设计,优化了用户体验。 增加了符合业务场景的流程校验与进度预览、引入富文本编辑器与代码编辑器。 结合后端引擎,可直接嵌入系统中使用。

详情请访问:https://www.bpmport.com/products ;

设计器预览:

  1. 编辑器:designer,
  2. 预览与模拟:viewer,
  3. DMN决策设计器:dmn

image.png

在开发流图编辑器的过程中,为了深度适配后端 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: string uri: string prefix: string xml?: { 
     typePrefix?: string tagAlias?: 'lowerCase' } types: ModelType[] enumerations?: EnumType[] associations?: unknown[] } type EnumType = { 
     name: string literalValues: LiteralValue[] } type LiteralValue = { 
     name: string } type ModelType = { 
     name: string isAbstract?: boolean superClass?: string[] extends?: string[] meta?: { 
     allowedIn?: string[] [key: string]: unknown } properties?: ModelProperty[] } type ModelProperty = { 
     name: string type: 'Boolean' | 'String' | 'Integer' | AnotherModelType isId?: boolean isAttr?: boolean isBody?: boolean isReference?: boolean isMany?: boolean xml?: { 
     serialize?: string typePrefix?: string } default?: string | number | boolean redefines?: string } 

关于 xml 业务逻辑部分的元素和属性声明,格式大概遵循上面这种类型定义。

至于 xml 中关于图形定义(图形类型、位置、大小等)部分,则 ModelProperty 中还会有 isVirtualsubsettedProperty 等属性,不过这些类型一般不涉及业务部分,对于我们的使用和扩展也没有多少关联,所以不用太过深入的了解。

Schema 与 ModelType 字段说明

在最外层的 Schema 中,主要有四个必要属性:

  • name:模型文件名,一般来说只需要描述这个模型文件的用途即可,没有实际作用。
  • prefix:前缀,用来区别不同元素模型,最常见的就是:bpmn、flowable、activiti、camunda,当然也可以自定义前缀。
  • uri:即统一资源标识符(Uniform Resource Identifier),定义该模型文件资源地址。
  • xml:指定配置 xml 的一些格式要求,目前只有 tagAliastypePrefix 两个属性,内部会比较 tagAlias 值是否是 lowerCase 来确定是否需要转换为小写驼峰;而 typePrefix 则是用于某些特定情况下的类型转换,当中新类型前缀,可选。
  • types:这里用于定义元素模型的所有元素和属性信息。
  • enumerationsassociations:这两个部分目前在实际业务中没有具体使用,可选。

在使用过程中,moddle 会解析所有 json schema 文件,生成一个 DescriptorTree。解析过程中,会校验文件中的 nameuri 属性值,如果存在相同值,则会抛出异常并忽略后面解析到的内容

至于 types 数组中的每个具体定义,参见 ModelType

  • name:属性/元素对应的类型名称(某些时候很有用~)。
  • isAbstract:确定这个类型是否可以通过 moddle.create 创建一个对象实例(实际上这个属性并没有实际作用,默认都可以通过 moddle.create 创建)。
  • meta: 元数据信息,一般来说都用来配置 allowedIn,设置该类型允许被添加到哪些类型对象下。
  • superClass:表示多继承,接受一个类型名称组成的数组,会继承数组内每个类型的所有属性。
  • extends:表示扩展,接受一个类型名称组成的数组,自身一般不会被直接使用,而是作为配置类型的补充,为配置类型增加新的属性配置。
  • properties:该类型对应的具体属性定义数组,类型为 ModelProperty[]

ModelProperty 的说明如下:

  • name:属性名称,作为该对象实例的一个属性键名。
  • type:该属性的类型,一般来说常用 BooleanString 两个参数来声明基础的属性,也可以用具体的某个 ModelType['name'] 对应的值,来表示该属性是一个指定类型的对象实例。
  • isId:标识属性是否可以作为 id 属性。
  • isAttr:标识该属性是否需要作为 xml 标签属性。
  • isBody:标识该属性是否需要显示在 xml 的标签内部。
  • isMany:是否是一个数组。
  • isReference:是否是通过 id 引用一个对象实例,在 xml 中显示为 id,而在 Javascript 运行中体现为对象。
  • redefines:是否是重定义,指定某个类型声明下的某个具体属性,用来覆盖该指定属性。
  • default:默认值,一般在 BooleanString 两个类型的属性中使用。
  • xml:xml 转换定义,包含 serializetypePrefix 两个定义,其中 serialize 常用的有两个值:xsi:typepropertytypePrefix 则没有固定值,但一般是使用单个字母。

关于 serializetypePrefix,两者与 xml 的生产和解析有关系,但比较复杂。具体影响后面会更新。

ModelProperty 中,部分属性的配置其实是 互斥 的,比如 isAttrisBodyisMany 等。

现在,我们已经了解了一个 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//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 结构如下:

image.png

当然,在使用 bpmn-js 的过程中,除了扩展元模型定义之外,很多时候还需要对原有的属性进行修改,也就是配置 redefines

但是在使用 redefines 时,还需要注意默认值的问题。在 bpmn-js(依赖的 bpmn-moddle => moddle-xml)中,如果属性值等于默认值,在 xml 中是不会显示的。

而当我们需要在等于默认值的情况下也显示到 xml 中的话,就需要对原有的属性进行重定义。

属性重定义

这里用一个例子来进行说明。

在 bpmn-js 中,AdHocSubProcess 临时子流程具有一个属性 cancelRemainingInstances,默认为 true,所以 xml 中会有如下情况:

image.png

使用 bpmn-js 解析该流程得到的两个子流程对应 js 对象如下:

image.png

此时可以看到虽然在 js 中我们可以获取到 cancelRemainingInstances 的实际值,但当其为 true 时无法显示到 xml 中。

那么这个属性在 Json Schema 中的定义是怎么样的呢?

{ 
     "name": "BPMN20" "types": [ { 
     "name": "AdHocSubProcess", "superClass": ["SubProcess"], "properties": [ { 
     "name": "cancelRemainingInstances", "default": true, "isAttr": true, "type": "Boolean" } ] } ] } 

省略了部分内容。

其中可以看到 isAttrtrue,所以这个属性直接显示在 AdHocSubProcess 标签上,默认值 defaulttrue,所以该属性值为 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 就会变成:

image.png

由于是另外一个文件声明,会带上 flowable 的前缀。

不过,虽然我们现在去掉了默认值,使得这个属性有值时都能显示在 xml 中,但是这也会带来一个问题。

重定义会遇到的问题

在之前的定义中,cancelRemainingInstances 具有默认值配置,所以即使 xml 中没有这个属性,也会将其解析为默认值 true。

而重定义之后,取消了默认值定义,那么此时必须按照 xml 中该属性对应的值来解析,如果不存在该属性,则在 js 中读取该元素 cancelRemainingInstances 属性的值便是 undefined

所以此时我们需要处理 在创建 AdHocSubProcess 时如何添加默认值 的问题。

至于设置默认值的问题,有几种解决思路:

  1. 创建完毕后手动赋值或者调api更新,但是会影响撤销恢复
  2. 通过 eventBus 在元素创建过程中赋值,这种方式比较完美,类似于 behavior,但是需要很了解 bpmn-js 的结构和执行逻辑
  3. 修改 bpmnFactory 模块,可能会对其他元素造成影响,但是还算方便

具体的实现方式,就留给各位小伙伴思考吧~

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/139847.html

(0)
上一篇 2025-06-01 17:45
下一篇 2025-06-01 18:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信