在前端开发中,cmmnjs模型和bpmnjs模型有许多类似可参照的地方,本文聚焦于cmmnjs的使用,与bpmn中类似的点将不做赘述。主要说明cmmn和bpmn的不同之处和节点的相关操作。
示例:项目地址 ,基于vue实现,但cmmnjs相关逻辑与框架无关,可直接迁移至其它框架。
代码中涉及到功能模块的部分,将直接以对应模块名称代替,实际开发时需要通过cmmnModeler.get('模块名')获取。
一、准入/准出条件的说明
在cmmn中,节点的状态触发是通过准入/准出条件判定的,满足条件后节点会进入激活/结束状态。没有设置条件时,激活状态默认自动进入,完成状态默认手动进入。比如某个节点没有设置开始条件,那么实例处于激活状态时,节点会自动进入激活状态,手动更改后进入完成状态。
准入/准出中的条件有onpart和ifpart两种,onpart是基于事件触发的条件(如其它节点的状态变化、监听器触发等),ifpart是基于数据触发的条件(如casefile节点中的数据满足某条件)。 两种可以单独使用也可以结合使用,onpart是会显示在画布上的逻辑连线,ifpart是直接定义表达式后绑定到对应的sentry元素上。
以下以准入条件onpart的使用为例作说明,准出条件可类推实现。
二、onpart的并或条件
上图流程,3有一个准入条件,连接1和2,代表当节点1和2达到完成(complete)状态时,3会进入激活状态。对应的xml文件为
<cmmn:planItem id="PlanItem_0ow40i6" definitionRef="Stage_1ruf9kj" />
<cmmn:planItem id="PlanItem_06ltn7h" definitionRef="Task_1kpf6ew" />
<cmmn:planItem id="PlanItem_1d65m02" definitionRef="Task_16wk69q" />
<cmmn:planItem id="PlanItem_01fkqvv" definitionRef="Task_1x1i026">
<cmmn:entryCriterion id="EntryCriterion_1o5063g" sentryRef="Sentry_1ha82xh" />
</cmmn:planItem>
<cmmn:sentry id="Sentry_1ha82xh">
<cmmn:planItemOnPart id="PlanItemOnPart_0hsjg3i" sourceRef="PlanItem_06ltn7h">
<cmmn:standardEvent>complete</cmmn:standardEvent>
</cmmn:planItemOnPart>
<cmmn:planItemOnPart id="PlanItemOnPart_11klvp8" sourceRef="PlanItem_1d65m02">
<cmmn:standardEvent>complete</cmmn:standardEvent>
</cmmn:planItemOnPart>
</cmmn:sentry>
<cmmn:stage id="Stage_1ruf9kj" />
<cmmn:task id="Task_1kpf6ew" name="1" />
<cmmn:task id="Task_16wk69q" name="2" />
<cmmn:task id="Task_1x1i026" name="3" />
可以看到,3的准入条件元素entryCriterion对应的定义sentry中,包裹着两个onpart条件,sourceRef属性分别指向1和2,其中包裹的standardEvent标签代表1和2需达成的状态。
比如当standardEvent=start时,表示1和2达到激活状态条件就满足。standardEvent=occur时,一般连接的是监听器,表示监听器被触发时满足条件。
类似地,上图流程代表3有两个准入条件,一个是1达成complete状态,一个是2达成complete状态,达成任一条件,3就会进入激活状态。对应的xml文件表现为
<cmmn:planItem id="PlanItem_1ufp8bu" definitionRef="Task_1c1tckr" />
<cmmn:planItem id="PlanItem_0wmaq4i" definitionRef="Task_14jigrz" />
<cmmn:planItem id="PlanItem_0ojydny" definitionRef="Task_0mllwhx">
<cmmn:entryCriterion id="EntryCriterion_1imiz17" sentryRef="Sentry_1k41jn1" />
<cmmn:entryCriterion id="EntryCriterion_1vqe6vt" sentryRef="Sentry_1rzd2ue" />
</cmmn:planItem>
<cmmn:sentry id="Sentry_1k41jn1">
<cmmn:planItemOnPart id="PlanItemOnPart_1xr9sg6" sourceRef="PlanItem_1ufp8bu">
<cmmn:standardEvent>complete</cmmn:standardEvent>
</cmmn:planItemOnPart>
</cmmn:sentry>
<cmmn:sentry id="Sentry_1rzd2ue">
<cmmn:planItemOnPart id="PlanItemOnPart_0khk2az" sourceRef="PlanItem_0wmaq4i">
<cmmn:standardEvent>complete</cmmn:standardEvent>
</cmmn:planItemOnPart>
</cmmn:sentry>
<cmmn:task id="Task_1c1tckr" name="1" />
<cmmn:task id="Task_14jigrz" name="2" />
<cmmn:task id="Task_0mllwhx" name="3" />
可以看到,任务3中包含两个准入条件元素,对应的sentry各自包裹一个onpart。
三、代码实现
通过代码实现以上流程时,节点和准入条件的添加可参考上文。在创建对应节点和准入条件后,因为onpart是会显示在画布上的连线,所以可以通过modeling.connect方法指定连线类型后创建。
// 通过modeling.connect创建元素之间的连线,参数12分别为连线的两个节点,参数3可指定连线类型
// standardEvent会自动设置,节点与节点的连线默认complete,节点与监听器的连线默认occur
// 创建后可通过connection.businessObject.cmmnElementRef.standardEvent更改
const connection = modeling.connect(task1, task3Entry, { type: 'cmmn:PlanItemOnPart' });
// 设置连线的isStandardEventVisible属性,可以控制连线上方的complete状态字样是否显示
modeling.updateProperties(connection, {
isStandardEventVisible: false,
});
// 连线的自定义渲染可以通过自定义renderer模块中的drawConnection方法实现,和节点的自定义渲染类似
在创建连线后,通过shape.incoming、shape.outcoming,可以获取某节点所有进入连线和出去连线。需要更改连线的起点终点时,可通过modeling.reconnectStart、modeling.reconnectEnd方法更改。
// 通过modeling.reconnectStart、modeling.reconnectEnd重新指定连线的起点和终点
// 参数一是连线,参数二是新起点/终点
// 参数三是连线在画布上的点的位置,{x,y}的对象数组,可参考connection原来的waypoints重新设置
let newWaypoints = [...connection.waypoints];
let newY = task2.y + task2.height / 2;
newWaypoints[0] = {x: newWaypoints[0].x, y: newY}
newWaypoints[1] = {x: newWaypoints[1].x, y: newY}
modeling.reconnectStart(connection, task2, newWaypoints);