在前端开发中,cmmnjs模型和bpmnjs模型有许多类似可参照的地方,本文聚焦于cmmnjs的使用,与bpmn中类似的点将不做赘述。主要说明cmmn和bpmn的不同之处和节点的相关操作。
示例:项目地址 ,基于vue实现,但cmmnjs相关逻辑与框架无关,可直接迁移至其它框架。
一、节点类型
与bpmn相比,cmmn最大的特点是无固定顺序,它可以在节点上添加准入/准出条件,满足条件时,对应节点会进入激活/完成状态。以下是一个简单的cmmn流程,一个案例中包含着一个任务节点。
对应的xml文件为
<?xml version="1.0" encoding="UTF-8"?>
<cmmn:definitions xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:cmmn="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/cmmn">
<cmmn:case id="Case_1">
<cmmn:casePlanModel id="CasePlanModel_1" name="A CasePlanModel">
<cmmn:planItem id="PlanItem_1" definitionRef="Task_1" />
<cmmn:task id="Task_1" name="Task" />
</cmmn:casePlanModel>
</cmmn:case>
<cmmndi:CMMNDI>
<cmmndi:CMMNDiagram id="CMMNDiagram_1">
<cmmndi:Size width="500" height="500" />
<cmmndi:CMMNShape id="DI_CasePlanModel_1" cmmnElementRef="CasePlanModel_1">
<dc:Bounds x="156" y="99" width="505" height="333" />
<cmmndi:CMMNLabel />
</cmmndi:CMMNShape>
<cmmndi:CMMNShape id="PlanItem_1_di" cmmnElementRef="PlanItem_1">
<dc:Bounds x="192" y="132" width="100" height="80" />
<cmmndi:CMMNLabel />
</cmmndi:CMMNShape>
</cmmndi:CMMNDiagram>
</cmmndi:CMMNDI>
</cmmn:definitions>
其中cmmn:case标签内是实际会被解析的流程,cmmndi:CMMNDI是对应的画布表现。
cmmn:casePlanModel代表一个案例,可以看到,casePlanModel标签内包裹着一个planItem和一个task,planItem的definitionRef属性指向task的id。
planItem是可执行元素的容器,task则是planitem的定义。通常说到的cmmn中的节点类型,指的是都是定义的类型task。
类似地,当给task节点上添加准入条件时,xml文件会表现为
<cmmn:planItem id="PlanItem_1" name="我是测试文本" definitionRef="Task_1">
<cmmn:entryCriterion id="EntryCriterion_0vbmr18" sentryRef="Sentry_0q8z91m" />
</cmmn:planItem>
<cmmn:sentry id="Sentry_0q8z91m" />
<cmmn:task id="Task_1" />
planItem中包裹一个准入条件的标签entryCriterion,对应定义会指向真正的条件哨兵sentry。
cmmn中常见的节点类型:
- CasePlanModel:案例根节点
- 普通节点 task:普通任务 humantask:人工干预的任务,需要人操作后才执行的任务 casetask:调用另一个案例,实现案例嵌套 processtask:调用另一个流程(bpmn流程) decisiontask:执行决策逻辑(需要先定义好dmn决策表) stage:阶段,逻辑分组,可嵌套其它阶段或任务 milestone:里程碑,用于标记阶段性目标
- 事件监听器:userEventListener、timerEventListener,用于监听用户事件、定时事件
- 准入/准出规则:entryCriterion、exitCriterion,元素的开始/结束条件,创建对应元素时,在xml文件中会一一对应一个哨兵,通过哨兵中包裹的onPart和ifPart判断是否满足条件
除了常见节点外,cmmn中还包含很多在实现特定功能时使用的节点。
caseFileItem(案例上下文数据)、caseParameter(定义案例的输入/输出数据)、 textAnnotation(说明文本、会显示在画布上)、documentation(元素可附加的文本节点,不会显示在画布上)、 manualActivationRule、repetitionRule(节点规则,是否手动开始、是否重复执行等)
有些是会展示在画布上的真实元素,有些是仅用于实现功能的逻辑元素。
二、模块
cmmn中包含很多功能模块,模块的修改和自定义可参照bpmn。此处介绍一下开发中常用的模块,源码位置一般在diagram-js或cmmn-js中的/lib/features或navigation。
- 基础模块:左侧栏palette、渲染renderer、元素点击后的功能框contextpad、事件监听eventBus
- 元素控制模块: modeling:放置/移动元素、更新元素属性等 moddle:创建自定义元素 elementRegistry:获取元素 elementFactory:元素工厂,可通过createPlanItemShape、createCriterionShape等方法快速创建对应类型元素 画布功能模块: canvas、graphicsFactory:获取画布上的元素图形 selection:控制元素的选择,通过select、deselect等方法控制元素的选中/取消选中 zoomScroll:控制画布的移动和缩放功能 handTool、lassoTool:拖动、框选工具,通过activate、toggle等方法激活/切换 directEditing:标签编辑功能,通过activate(shape)方法可以激活画布上某个元素的标签编辑
- 其它 commandStack:命令栈,cmmnjs中的每个操作都是下达了一个命令,会保存在commandStack中,通过undo、redo撤销/重做命令 rules:规则模块,控制元素在移动、连线时是否被允许的规则,在自定义该模块时可以通过addRule函数添加新规则。例如:
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
export default class CustomRules extends RuleProvider {
constructor(eventBus) {
super(eventBus);
}
init() {
// 创建连线
// 1500是优先级,需高于默认优先级1000,在默认规则前执行
this.addRule('connection.create', 1500, (context) => {
// 不可连线 返回false
// 可连线时,要么不返回(遵循默认规则的返回)
// 要么返回具体的连线类型,如
// return { type: 'cmmn:PlanItemOnPart' }(元素与哨兵的连接,定义触发的onpart条件)
// return { type: 'cmmn:Association' }(元素与注释信息的连接)
});
// 创建元素
this.addRule('shape.create', 1500, function (context) {
// 返回true/false
});
// 元素移动
this.addRule('elements.move', 1500, function (context) {
// 返回true/false
});
}
}
CustomRules.$inject = ['eventBus'];
三、在项目中引入cmmnjs
1、安装依赖:cmmn-js、diagram-js 2、引入必要的样式文件
@import "cmmn-js/dist/assets/diagram-js.css";
@import "cmmn-js/dist/assets/cmmn-font/css/cmmn.css";
@import "cmmn-js/dist/assets/cmmn-font/css/cmmn-embedded.css";
3、初始化
import CmmnModeler from "cmmn-js/lib/Modeler";
// container可以直接传对应的dom元素或其id
cmmnModeler = new CmmnModeler({
container: container, // '#js-canvas'
});
4、引入xml文件
// diagramXML是xml文本
cmmnModeler.importXML(diagramXML, function (err) {
if (err) {
console.error("导入出错", err);
return;
}
console.log("导入成功");
});