因为工作须要(其实不是很须要),在公司我的项目的根底上开源了一个基于 bpmn-js + Vue 2.x + ElementUI 的一个流程编辑器 Bpmn Process Designer, 预览地址 MiyueFE blog, 欢送 fork 和 star。
文章首发于 掘金 Bpmn.js 中文文档(二),转载请注明出处。
对于 bpmn.js
的简略应用,我在 Bpmn.js 中文文档(一)中做了简略形容,并对其中几个外围模块的 api 做了阐明,本文将继上文持续对 bpmn.js
的功能模块 api 做简略阐明。
四. Modules
7. Modeling 根本建模办法
Diagram.js
提供的根底建模工厂 BaseModeling
,注入了 EventBus, ElementFactory, CommandStack
模块。Bpmn.js
继承了 BaseModeling
并提供了新的办法。
该模块在自定义节点属性等方面常常应用
应用形式
const Modeling = this.bpmnModeler.get("modeling");
Modeling
初始化时会向 CommandStack
命令堆栈中注册对应的处理程序,以确保操作可复原和勾销。
Modeling
提供的办法次要是依据 handlers
来定义的,每个办法会触发对应的事件
// BaseModeling (diagram.js)
BaseModeling.prototype.getHandlers = function () {
var BaseModelingHandlers = {
'shape.append': AppendShapeHandler, // 形态可逆增加到源形态的处理程序
'shape.create': CreateShapeHandler, // 形态可逆创立、增加到流程中的处理程序
'shape.delete': DeleteShapeHandler, // 形态可逆移除的处理程序
'shape.move': MoveShapeHandler, // 形态可逆挪动的处理程序
'shape.resize': ResizeShapeHandler, // 形态可逆变换大小的处理程序
'shape.replace': ReplaceShapeHandler, // 通过增加新形态并删除旧形态来替换形态。如果可能,将放弃传入和传出连贯
'shape.toggleCollapse': ToggleShapeCollapseHandler, // 切换元素的折叠状态及其所有子元素的可见性
'spaceTool': SpaceToolHandler, // 通过挪动和调整形态、大小、连线锚点 (巡航点) 来增加或者删除空间
'label.create': CreateLabelHandler, // 创立标签并附加到特定的模型元素上
'connection.create': CreateConnectionHandler, // 创立连线,并显示到画布上
'connection.delete': DeleteConnectionHandler, // 移除连线
'connection.move': MoveConnectionHandler, // 实现连贯的可逆挪动的处理程序。该处理程序与布局连贯处理程序的不同之处在于它保留了连贯布局
'connection.layout': LayoutConnectionHandler, // 实现形态的可逆挪动的处理程序
'connection.updateWaypoints': UpdateWaypointsHandler, // 更新锚点(巡航点)
'connection.reconnect': ReconnectConnectionHandler, // 从新建设连贯关系
'elements.create': CreateElementsHandler, // 元素可逆创立的处理程序
'elements.move': MoveElementsHandler, // 元素可逆挪动的处理程序
'elements.delete': DeleteElementsHandler, // 元素可逆移除的处理程序
'elements.distribute': DistributeElementsHandler, // 平均调配元素布局的处理程序
'elements.align': AlignElementsHandler, // 以某种形式对齐元素
'element.updateAttachment': UpdateAttachmentHandler // 实现形态的可逆附着 / 拆散的处理程序。}
return BaseModelingHandlers;
}
// Modeling (bpmn.js)
var ModelingHandlers = BaseModeling.prototype.getHandlers.call(this);
ModelingHandlers['element.updateModdleProperties'] = UpdateModdlePropertiesHandler; // 实现元素上的扩大属性的可逆批改
ModelingHandlers['element.updateProperties'] = UpdatePropertiesHandler; // 实现元素上的属性的可逆批改
ModelingHandlers['canvas.updateRoot'] = UpdateCanvasRootHandler; // 可逆更新画布挂载节点
ModelingHandlers['lane.add'] = AddLaneHandler; // 可逆通道增加
ModelingHandlers['lane.resize'] = ResizeLaneHandler; // 通道可逆 resize
ModelingHandlers['lane.split'] = SplitLaneHandler; // 通道可逆分隔
ModelingHandlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler; // 可逆更新通道援用
ModelingHandlers['id.updateClaim'] = IdClaimHandler;
ModelingHandlers['element.setColor'] = SetColorHandler; // 可逆更新元素色彩
ModelingHandlers['element.updateLabel'] = UpdateLabelHandler; // 可逆更新元素 label
提供办法
const Modeling = this.bpmnModeler.get("modeling");
// 获取以后领有的处理程序
Modeling.getHandlers()
/**
* 更新元素的 label 标签,同时触发 element.updateLabel 事件
* @param element: ModdleElement
* @param newLabel: ModdleElement 新的标签元素
* @param newBounds: {x: number;y: number; width: number; height: number} 地位及大小
* @param hints?:{} 提示信息
*/
Modeling.updateLabel(element, newLabel, newBounds, hints);
/**
* 创立新的连接线,触发 connection.create 事件
* 会在外部调用 createConnection() 办法(Modeling.prototype.createConnection -- in diagram.js)* @param source:ModdleElement 源元素
* @param target:ModdleElement 指标元素
* @param attrs?: {} 属性,未传时会依据规定替换成对应的对象,次要蕴含连线类型 type
* @param hints?: {}
* @return Connection 连线实例
*/
Modeling.connect(source, target, attrs, hints)
/**
* 更新元素扩大属性,同时触发 element.updateModdleProperties
* @param element 指标元素
* @param moddleElement 元素扩大属性对应的实例
* @param properties 属性
*/
Modeling.updateModdleProperties(element, moddleElement, properties)
/**
* 更新元素属性,同时触发 element.updateProperties
* @param element 指标元素
* @param properties 属性
*/
Modeling.connect(element, properties)
/**
* 泳道 (通道) 事件,会触发对应的事件 lane.resize
*/
Modeling.resizeLane(laneShape, newBounds, balanced)
/**
* 泳道 (通道) 事件,会触发对应的事件 lane.add
*/
Modeling.addLane(targetLaneShape, location)
/**
* 泳道 (通道) 事件,会触发对应的事件 lane.split
*/
Modeling.splitLane(targetLane, count)
/**
* 将以后图转换为合作图
* @return Root
*/
Modeling.makeCollaboration()
/**
* 将以后图转换为一个过程
* @return Root
*/
Modeling.makeProcess()
/**
* 批改指标元素 color,同时触发 element.setColor 事件
* @param elements: ModdleElment || ModdleElement[] 指标元素
* @param colors:{[key: string]: string} svg 对应的 css 色彩属性对象
*/
Modeling.setColor(elements, colors)
BaseModeling
提供办法
BaseModeling
为 diagram.js
提供的根底办法,也能够间接调用未被 bpmn.js
笼罩的办法。
// 向命令堆栈注册处理程序
Modeling.registerHandlers(commandStack)
// 挪动 Shape 元素到新元素下,触发 shape.move
Modeling.moveShape(shape, delta, newParent, newParentIndex, hints)
// 挪动多个 Shape 元素到新元素下,触发 elements.move
Modeling.moveElements(shapes, delta, target, hints)
// 挪动 Connection 元素到新元素下,触发 connection.move
Modeling.moveConnection(connection, delta, newParent, newParentIndex, hints)
// 挪动 Connection 元素到新元素下,触发 connection.move
Modeling.layoutConnection(connection, hints)
/**
* 创立新的连线实例,触发 connection.create
* @param source: ModdleElement
* @param target: ModdleElement
* @param parentIndex?: number
* @param connection: ModdleElement | Object 连线实例或者配置的属性对象
* @param parent:ModdleElement 所在的元素的父元素 通常为 Root
* @param hints: {}
* @return Connection 新的连线实例
*/
Modeling.createConnection(source, target, parentIndex, connection, parent, hints)
/**
* 创立新的图形实例,触发 shape.create
* @param shape
* @param position
* @param target
* @param parentIndex
* @param hints
* @return Shape 新的图形实例
*/
Modeling.createShape(shape, position, target, parentIndex, hints)
/**
* 创立多个元素实例,触发 elements.create
* @param
* @param
* @return Elements 实例数组
*/
Modeling.createElements(elements, position, parent, parentIndex, hints)
/**
* 为元素创立 label 实例,触发 label.create
* @param labelTarget: ModdleElement 指标元素
* @param position: {x: number; y: number}
* @param label:ModdleElement label 实例
* @param parent: ModdleElement
* @return Label
*/
Modeling.createLabel(labelTarget, position, label, parent)
/**
* 将形态附加到给定的源,在源和新创建的形态之间绘制连贯。触发 shape.append
* @param source: ModdleElement
* @param shape: ModdleElement | Object
* @param position: {x: number; y: number}
* @param target: ModdleElement
* @param hints
* @return Shape 形态实例
*/
Modeling.appendShape(source, shape, position, target, hints)
/**
* 移除元素,触发 elements.delete
* @param elements: ModdleElement[]
*/
Modeling.removeElements(elements)
/**
* 不太理解
*/
Modeling.distributeElements(groups, axis, dimension)
/**
* 移除元素, 触发 shape.delete
* @param shape:ModdleElement
* @param hints?: object
*/
Modeling.removeShape(shape, hints)
/**
* 移除连线, 触发 connection.delete
* @param connection:ModdleElement
* @param hints?: object
*/
Modeling.removeConnection(connection, hints)
/**
* 更改元素类型(替换元素),触发 shape.replace
* @param oldShape:ModdleElement
* @param newShape:ModdleElement
* @param hints?: object
* @return Shape 替换后的新元素实例
*/
Modeling.replaceShape(oldShape, newShape, hints)
/**
* 对其选中元素,触发 shape.replace
* @param elements: ModdleElement[]
* @param alignment: Alignment
* @return
*/
Modeling.alignElements(elements, alignment)
/**
* 调整形态元素大小,触发 shape.resize
* @param shape: ModdleElement
* @param newBounds
* @param minBounds
* @param hints?: object
*/
Modeling.resizeShape(shape, newBounds, minBounds, hints)
/**
* 切换元素开展 / 膨胀模式,触发 shape.toggleCollapse
* @param shape?: ModdleElement
* @param hints?: object=
*/
Modeling.toggleCollapse(shape, hints)
// 连线调整的办法
Modeling.reconnect(connection, source, target, dockingOrPoints, hints)
Modeling.reconnectStart(connection, newSource, dockingOrPoints, hints)
Modeling.reconnectEnd(connection, newTarget, dockingOrPoints, hints)
Modeling.connect(source, target, attrs, hints)
8. Draw 绘制模块
根底的元素绘制办法,由 diagram.js
提供根底模块,源码如下:
// diagram.js/lib/draw/index.js
import DefaultRenderer from './DefaultRenderer';
import Styles from './Styles';
export default {__init__: [ 'defaultRenderer'],
defaultRenderer: ['type', DefaultRenderer],
styles: ['type', Styles]
};
其中 DefaultRenderer
为默认元素绘制办法,继承 BaseRenderer
,本身蕴含 CONNECTION_STYLE -- 连线默认款式
, FRAME_TYLE -- 框架默认款式
和 SHAPE_STYLE -- 元素默认款式
三个款式属性。
Styles
为款式治理组件,蕴含 cls -- 依据属性、款式名等来定义款式
, style -- 依据属性计算款式
和 computeStyle -- 款式计算方法
三个办法。
BaseRenderer
是一个抽象类,只定义了办法和绘制时的触发事件,没有定义方法的具体实现。
Styles
款式治理(diagram.js
)
依据源码的思路,这个模块只举荐重写,即批改默认的类名与款式配置。
// diagram.js/lib/draw/Styles.js
import {isArray, assign, reduce} from 'min-dash';
/**
* A component that manages shape styles
*/
export default function Styles() {
var defaultTraits = {
'no-fill': {fill: 'none'},
'no-border': {strokeOpacity: 0.0},
'no-events': {pointerEvents: 'none'}
};
var self = this;
/**
* Builds a style definition from a className, a list of traits and an object of additional attributes.
*
* @param {string} className
* @param {Array<string>} traits
* @param {Object} additionalAttrs
*
* @return {Object} the style defintion
*/
this.cls = function(className, traits, additionalAttrs) {var attrs = this.style(traits, additionalAttrs);
return assign(attrs, { 'class': className});
};
/**
* Builds a style definition from a list of traits and an object of additional attributes.
*
* @param {Array<string>} traits
* @param {Object} additionalAttrs
*
* @return {Object} the style defintion
*/
this.style = function(traits, additionalAttrs) {if (!isArray(traits) && !additionalAttrs) {
additionalAttrs = traits;
traits = [];}
var attrs = reduce(traits, function(attrs, t) {return assign(attrs, defaultTraits[t] || {});
}, {});
return additionalAttrs ? assign(attrs, additionalAttrs) : attrs;
};
this.computeStyle = function(custom, traits, defaultStyles) {if (!isArray(traits)) {
defaultStyles = traits;
traits = [];}
return self.style(traits || [], assign({}, defaultStyles, custom || {}));
};
}
DefaultRenderer
默认绘制办法(diagram.js
)
源码地位:
diagram-js/lib/draw/DefaultRenderer.js
继承了 diagram.js/BaseRenderer
,注入 eventBus
styles
模块,并且默认绘制办法的解决优先级最低,在有其余绘制办法的时候会被笼罩。
BaseRenderer
提供了一个形象基类,并且提供了 canRender() , getShapePath(), getConnecttionPath(), drawShape(), DrawConnection()
五个形象办法,定义了办法触发时刻。
eventBus.on(['render.shape', 'render.connection'], renderPriority, function(evt, context) {
var type = evt.type,
element = context.element,
visuals = context.gfx;
if (self.canRender(element)) {if (type === 'render.shape') {return self.drawShape(visuals, element);
} else {return self.drawConnection(visuals, element);
}
}
});
eventBus.on(['render.getShapePath', 'render.getConnectionPath'], renderPriority, function(evt, element) {if (self.canRender(element)) {if (evt.type === 'render.getShapePath') {return self.getShapePath(element);
} else {return self.getConnectionPath(element);
}
}
});
DefaultRenderer
重写了以上五个办法(canRender()
间接返回了 true
,示意任何状况都能够绘制和渲染元素),实现默认元素和款式的解析渲染。
办法阐明:
canRender()
: 判断办法,返回一个布尔值,为真时示意能够持续解析元素属性(地位、大小、形态等)或者持续渲染属性。getShapePath(shape)
: 元素(默认是方形元素)属性解析办法。getConnectionPath(connection)
: 连线属性解析办法。drawShape(visuals, element)
: 元素(默认是方形元素)绘制办法。drawConnection(visuals, connection)
: 连线绘制办法。
——————————— 分割线 ————————————-
bpmn.js
继承diagram.js/BaseRenderer
定义了一个BpmnRender
类,并针对bpmn 2.0
流程须要的其余元素做了新的解决。
bpmn.js
为了实现 bpmn 2.0
流程图的反对,不仅从新定义了新的渲染办法类 BpmnRenderer, TextRender, PathMap
,以保障图形元素的失常解析,以及 label
的便捷增加批改。
import BpmnRenderer from './BpmnRenderer';
import TextRenderer from './TextRenderer';
import PathMap from './PathMap';
export default {__init__: [ 'bpmnRenderer'],
bpmnRenderer: ['type', BpmnRenderer],
textRenderer: ['type', TextRenderer],
pathMap: ['type', PathMap]
};
BpmnRenderer
流程元素绘制办法
反对 bpmn 2.0
的流程元素的根底绘制办法,继承 BaseRender
,注入了 config, eventBus, styles, pathMap, canvas, textRenderer
模块。源码位于 bpmn-js/lib/draw/BpmnRenderer.js
,共 1900+ 行(其中 1200+ 行都在定义绘制各种元素的办法)。
BpmnRenderer
只实现了基类的 4 个形象办法(getConnectionPath()
办法没有应用,由此可见其实 bpmn-js
外部的连线元素也是当做了 shape
类型来进行解决的,毕竟有个箭头,也可能存在折线的状况),并且没有新增办法。然而在 canRender()
办法里判断了须要渲染的元素是否属于 bpmn:BaseElement
类型。
BpmnRenderer.prototype.canRender = function(element) {return is(element, 'bpmn:BaseElement'); // 从解析文件 bpmn.json 其实能够发现,所有须要渲染的元素最终都继承了 Bpmn:BaseElement
};
在 getShapePath()
办法中,对属于 bpmnEvent(事件类节点,例如开始和完结等事件,显示为圆形)
,bpmn:Activity(工作类节点,蕴含子流程类型的节点,显示为圆角矩形)
,bpmn:Gateway(网关类型,显示为菱形)
三个大类型的节点定义的对应的门路获取办法,其余类型则沿用与 diagram.js/DefaultRenderer.js
外面应用的 getRectPath()
办法。
drawShape()
与 drawConnection()
办法则是判断了须要渲染的元素类型,调用对应的 handler()
办法也解决(也就是下面说的那 1200+ 行代码),通过 handlers
对象(所有 handler()
办法的汇合,以各类型的类型名作为 key
),能够发现可显示的元素一共有 60 种:
0: "bpmn:Event"
1: "bpmn:StartEvent"
2: "bpmn:MessageEventDefinition"
3: "bpmn:TimerEventDefinition"
4: "bpmn:EscalationEventDefinition"
5: "bpmn:ConditionalEventDefinition"
6: "bpmn:LinkEventDefinition"
7: "bpmn:ErrorEventDefinition"
8: "bpmn:CancelEventDefinition"
9: "bpmn:CompensateEventDefinition"
10: "bpmn:SignalEventDefinition"
11: "bpmn:MultipleEventDefinition"
12: "bpmn:ParallelMultipleEventDefinition"
13: "bpmn:EndEvent"
14: "bpmn:TerminateEventDefinition"
15: "bpmn:IntermediateEvent"
16: "bpmn:IntermediateCatchEvent"
17: "bpmn:IntermediateThrowEvent"
18: "bpmn:Activity"
19: "bpmn:Task"
20: "bpmn:ServiceTask"
21: "bpmn:UserTask"
22: "bpmn:ManualTask"
23: "bpmn:SendTask"
24: "bpmn:ReceiveTask"
25: "bpmn:ScriptTask"
26: "bpmn:BusinessRuleTask"
27: "bpmn:SubProcess"
28: "bpmn:AdHocSubProcess"
29: "bpmn:Transaction"
30: "bpmn:CallActivity"
31: "bpmn:Participant"
32: "bpmn:Lane"
33: "bpmn:InclusiveGateway"
34: "bpmn:ExclusiveGateway"
35: "bpmn:ComplexGateway"
36: "bpmn:ParallelGateway"
37: "bpmn:EventBasedGateway"
38: "bpmn:Gateway"
39: "bpmn:SequenceFlow"
40: "bpmn:Association"
41: "bpmn:DataInputAssociation"
42: "bpmn:DataOutputAssociation"
43: "bpmn:MessageFlow"
44: "bpmn:DataObject"
45: "bpmn:DataObjectReference"
46: "bpmn:DataInput"
47: "bpmn:DataOutput"
48: "bpmn:DataStoreReference"
49: "bpmn:BoundaryEvent"
50: "bpmn:Group"
51: "label"
52: "bpmn:TextAnnotation"
53: "ParticipantMultiplicityMarker"
54: "SubProcessMarker"
55: "ParallelMarker"
56: "SequentialMarker"
57: "CompensationMarker"
58: "LoopMarker"
59: "AdhocMarker"
具体的实现办法,有趣味的童鞋能够自行查看。
要实现自定义
renderer
,实质也是定义一个本人的渲染函数类,继承BaseRenderer
,而后批改原型链上的办法,使生成的渲染办法实例每次调用drawShape()
或者drawConnection()
等办法的时候都调用自定义的绘制办法(这里必须从新实现基类BaseRenderer
的几个办法,否则不失效)。
TextRenderer
文本元素绘制办法
源码位于 bpmn-js/lib/draw/TextRenderer.js
,次要实现了文字元素(即 Label
标签)的渲染与显示,通过获取绑定节点的地位和大小,在对应的地位生成一个 text
标签来显示文本。可通过重写该函数类来实现自定义的文本地位管制。
PathMap
SVG 元素门路对象
蕴含 BpmnRenderer
所需的 SVG 门路的函数,外部有一个 pathMap
对象,保留了所有的元素的 svg 门路、默认大小。
9. AlignElements 元素对齐
diagram.js
模块,注入模块 Modeling
。次要用作元素对齐。
会依照元素对齐方向的边界对齐。
应用:
const AlignElements = this.bpmnModeler.get("alignElements");
/**
* Executes the alignment of a selection of elements
* 执行元素抉择的对齐
*
* @param {Array} elements 通常为节点元素
* @param {string} type 可用:left|right|center|top|bottom|middle
*/
AlignElements.trigger(Elements, type);
改写:
// index.js
import CustomElements from './CustomElements';
export default {__init__: [ 'customElements'],
customElements: ['type', CustomElements]
};
// CustomElements.js
import inherits from 'inherits';
import AlignElements from 'diagrem-js/lib/features/align-elements/AlignElements';
export default function CustomElements(modeling) {this._modeling = modeling;}
inherits(CustomElements, AlignElements);
CustomElements.$inject = ['modeling'];
CustomElements.prototype.trigger = function(elements, type) {// 对齐逻辑}
10. AttachSupport 附丽反对
diagram.js
模块,注入模块 injector, eventBus, canvas, rules, modeling
,依赖规定模块 rulesModule
。次要用作元素挪动期间的绑定关系和预览。
根底逻辑模块,不举荐更改,也不提供间接应用的办法。
11. AutoPlace 元素主动搁置
将元素主动搁置到适合地位(默认在前方,正后方存在元素时向右下偏移)并调整连贯的办法。通常在点击 contentPad
创立新元素的时候触发。
注入根底模块 eventBus
与 modeling
。
默认会初始化一个搁置后选中元素的办法。
应用:
const AutoPlace = this.bpmnModeler.get("autoPlace");
/**
* Append shape to source at appropriate position.
* 将形态增加的源对应的适合地位
* 会触发 autoPlace.start autoPlace autoPlace.end 三个事件
*
* @param {djs.model.Shape} source ModdleElement
* @param {djs.model.Shape} shape ModdleElement
*
* @return {djs.model.Shape} appended shape
*/
AutoPlace.append(source, shape, hints);
12. AutoResize 元素大小调整
一个主动调整大小的组件模块,用于在创立或挪动子元素靠近父边缘的状况下扩大父元素。
注入模块 eventBus, elementRegistry, modeling, rules
临时没找到怎么间接调用
重写 / 禁用:
// 重写
// index.js
import AutoResize from './AutoResize';
export default {__init__: [ 'autoResize'],
autoResize: ['type', AutoResize]
// 禁用间接设置 autoResize: ['type', ""] 或者 autoResize: ['type',()=> false]
};
// AutoResize.js
export default function AutoResize(eventBus, elementRegistry, modeling, rules) {// ...}
AutoResize.$inject = [
'eventBus',
'elementRegistry',
'modeling',
'rules'
];
inherits(AutoResize, CommandInterceptor); // CommandInterceptor 向 commandStack 中插入命令的原型。
13. AutoScroll 画布滚动
画布主动扩大滚动的办法,如果以后光标点凑近边框,则开始画布滚动。当以后光标点移回到滚动边框内时勾销或手动勾销。
依赖于 DraggingModule
,注入模块 eventBus, canvas
函数原型上传入了
config
,但打印为undefined
应用与办法:
const AutoScroll = this.modeler.get("autoScroll");
/**
* Starts scrolling loop.
* 开始滚动
* Point is given in global scale in canvas container box plane.
*
* @param {Object} point {x: X, y: Y}
*/
AutoScroll.startScroll(point);
// 进行滚动
AutoScroll.stopScroll();
/**
* 笼罩默认配置
* @param {Object} options
* options.scrollThresholdIn: [20, 20, 20, 20],
* options.scrollThresholdOut: [0, 0, 0, 0],
* options.scrollRepeatTimeout: 15,
* options.scrollStep: 10
*/
AutoScroll.setOptions(options);