因为工做须要(其实不是很须要),在公司项目的基础上开源了一个基于 bpmn-js + Vue 2.x + ElementUI 的一个流程编辑器 Bpmn Process Designer, 预览地址 MiyueFE blog, 欢迎 fork 和 star。
文章首发于 掘金 Bpmn.js 中文文档(二),转载请注明出处。
关于 bpmn.js
的简单使用,我在 Bpmn.js 中文文档(一) 中作了简单描述,并对其中几个核心模块的 api 作了说明,本文将继上文继续对 bpmn.js
的功能模块 api 作简单说明。javascript
Diagram.js
提供的基础建模工厂 BaseModeling
,注入了 EventBus, ElementFactory, CommandStack
模块。Bpmn.js
继承了 BaseModeling
并提供了新的方法。css
该模块在自定义节点属性等方面常常使用java
const Modeling = this.bpmnModeler.get("modeling");
Modeling
初始化时会向 CommandStack
命令堆栈中注册对应的处理程序,以确保操做可恢复和取消。git
Modeling
提供的方法主要是根据 handlers
来定义的,每一个方法会触发对应的事件github
// 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
覆盖的方法。json
// 向命令堆栈注册处理程序 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)
基础的元素绘制方法,由 diagram.js
提供基础模块,源码以下:canvas
// 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 -- 元素默认样式
三个样式属性。api
Styles
为样式管理组件,包含 cls -- 根据属性、样式名等来定义样式
, style -- 根据属性计算样式
和 computeStyle -- 样式计算方法
三个方法。数组
BaseRenderer
是一个抽象类,只定义了方法和绘制时的触发事件,没有定义方法的具体实现。
Styles
样式管理(diagram.js
)根据源码的思路,这个模块只推荐重写,即修改默认的类名与样式配置。app
// 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 路径、默认大小。
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) { // 对齐逻辑 }
diagram.js
模块,注入模块 injector, eventBus, canvas, rules, modeling
,依赖规则模块 rulesModule
。主要用做元素移动期间的绑定关系和预览。
基础逻辑模块,不推荐更改,也不提供直接使用的方法。
将元素自动放置到合适位置(默认在后方,正后方存在元素时向右下偏移)并调整链接的方法。一般在点击 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);
一个自动调整大小的组件模块,用于在建立或移动子元素接近父边缘的状况下扩展父元素。
注入模块 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中插入命令的原型。
画布自动扩展滚动的方法,若是当前光标点靠近边框,则开始画布滚动。 当当前光标点移回到滚动边框内时取消或手动取消。
依赖于 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);