cmmnjs的前端实践:5、准入/准出条件的使用

94 阅读3分钟

在前端开发中,cmmnjs模型和bpmnjs模型有许多类似可参照的地方,本文聚焦于cmmnjs的使用,与bpmn中类似的点将不做赘述。主要说明cmmn和bpmn的不同之处和节点的相关操作。

示例:项目地址 ,基于vue实现,但cmmnjs相关逻辑与框架无关,可直接迁移至其它框架。

代码中涉及到功能模块的部分,将直接以对应模块名称代替,实际开发时需要通过cmmnModeler.get('模块名')获取。

一、准入/准出条件的说明

在cmmn中,节点的状态触发是通过准入/准出条件判定的,满足条件后节点会进入激活/结束状态。没有设置条件时,激活状态默认自动进入,完成状态默认手动进入。比如某个节点没有设置开始条件,那么实例处于激活状态时,节点会自动进入激活状态,手动更改后进入完成状态。

准入/准出中的条件有onpart和ifpart两种,onpart是基于事件触发的条件(如其它节点的状态变化、监听器触发等),ifpart是基于数据触发的条件(如casefile节点中的数据满足某条件)。 两种可以单独使用也可以结合使用,onpart是会显示在画布上的逻辑连线,ifpart是直接定义表达式后绑定到对应的sentry元素上。

以下以准入条件onpart的使用为例作说明,准出条件可类推实现。

二、onpart的并或条件

m2IMDCNEmb5BXLIA.png

上图流程,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时,一般连接的是监听器,表示监听器被触发时满足条件。

vLCCBxiSyJgpyHzj.png

类似地,上图流程代表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);