因爲工做須要(其實不是很須要),在公司項目的基礎上開源了一個基於 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);