bpmn.js 通过 EventBus(事件总线)来处理用户交互与内部状态变化。本文讲解如何监听事件、触发操作、与流程界面互动,从而构建反应性的流程应用。
EventBus的基本概念
EventBus 是 diagram-js 的核心,所有交互与状态变化都会发出事件。通过监听事件,我们可以:
- 捕获用户操作(点击、拖拽)
- 响应内部变化(元素创建、属性修改)
- 触发自定义逻辑(验证、通知、保存)
事件的三个阶段
1 2 3 4 5
| 前置事件 执行 后置事件 pre:xxx → (操作发生) → post:xxx
例: pre:execute → (命令执行) → post:execute
|
常见事件与用法
1. 元素交互事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| const eventBus = modeler.get('eventBus');
eventBus.on('element.click', function(event) { const element = event.element; console.log('点击了:', element.id, element.businessObject.name); showProperties(element); });
eventBus.on('element.dblclick', function(event) { const element = event.element; console.log('双击了:', element.id); editElement(element); });
eventBus.on('element.contextmenu', function(event) { event.preventDefault(); const element = event.element; console.log('右键菜单:', element.id); showContextMenu(event, element); });
eventBus.on('element.hover', function(event) { const element = event.element; console.log('悬停:', element.id); highlightRelatedElements(element); });
eventBus.on('element.out', function(event) { const element = event.element; console.log('离开:', element.id); clearHighlight(element); });
|
2. 选中事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| eventBus.on('selection.changed', function(event) { const newSelection = event.newSelection; const deselected = event.deselected; console.log('选中元素:', newSelection.map(el => el.id)); console.log('取消选中:', deselected.map(el => el.id)); if (newSelection.length > 0) { const element = newSelection[0]; updatePropertiesPanel(element); } else { clearPropertiesPanel(); } });
|
3. 元素生命周期事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| eventBus.on('pre:shape.create', function(event) { const shape = event.context.shape; console.log('即将创建:', shape.type); if (!isValidShape(shape)) { event.stopPropagation(); console.log('✗ 创建被拒绝'); } });
eventBus.on('shape.added', function(event) { const shape = event.element; console.log('✓ 元素已添加:', shape.id); });
eventBus.on('pre:shape.delete', function(event) { const shape = event.context.shape; console.log('即将删除:', shape.id); if (!confirmDelete(shape)) { event.stopPropagation(); } });
eventBus.on('shape.removed', function(event) { console.log('✓ 元素已删除:', event.element.id); });
|
4. 连接事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| eventBus.on('connection.created', function(event) { const connection = event.connection; console.log('✓ 连接已创建:', connection.source.id, '->', connection.target.id); updateConnectionLabel(connection); });
eventBus.on('pre:connection.delete', function(event) { const connection = event.context.connection; console.log('即将删除连接'); if (!confirmDeleteConnection(connection)) { event.stopPropagation(); } });
eventBus.on('connection.add', function(event) { const connection = event.element; if (connection.businessObject.conditionExpression) { modeler.get('canvas').addMarker(connection, 'conditional'); } });
|
5. 属性变更事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| eventBus.on('element.changed', function(event) { const element = event.element; console.log('元素属性已变更:', element.id); if (element.type === 'bpmn:UserTask') { console.log('任务名称:', element.businessObject.name); onElementChanged(element); } });
eventBus.on('businessObject.updated', function(event) { const bo = event.businessObject; console.log('业务对象已更新:', bo); });
|
6. 命令执行事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const commandStack = modeler.get('commandStack');
eventBus.on('pre:execute', function(event) { const command = event.command; console.log('即将执行命令:', command.context); });
eventBus.on('post:execute', function(event) { console.log('✓ 命令已执行'); autoSaveProcess(); });
commandStack.on('post:execute', async () => { console.log('流程已修改,准备保存...'); await saveProcessToServer(); });
|
实战案例:构建完整的编辑界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| class ProcessEditor { constructor(containerId) { this.modeler = new BpmnJS({ container: containerId }); this.setupEventListeners(); } setupEventListeners() { const eventBus = this.modeler.get('eventBus'); const commandStack = this.modeler.get('commandStack'); eventBus.on('selection.changed', (event) => { if (event.newSelection.length > 0) { this.showProperties(event.newSelection[0]); } else { this.clearProperties(); } }); eventBus.on('element.click', (event) => { this.highlightElement(event.element); }); commandStack.on('post:execute', async () => { await this.autoSave(); }); eventBus.on('element.contextmenu', (event) => { event.preventDefault(); this.showContextMenu(event, event.element); }); } showProperties(element) { const panel = document.getElementById('properties-panel'); panel.innerHTML = ` <div class="property"> <label>ID:</label> <span>${element.id}</span> </div> <div class="property"> <label>类型:</label> <span>${element.type}</span> </div> <div class="property"> <label>名称:</label> <input type="text" value="${element.businessObject.name || ''}" onchange="editor.updateProperty(this)"> </div> `; } clearProperties() { document.getElementById('properties-panel').innerHTML = ''; } highlightElement(element) { const canvas = this.modeler.get('canvas'); const registry = this.modeler.get('elementRegistry'); registry.getAll().forEach(el => { canvas.removeMarker(el, 'highlight'); }); canvas.addMarker(element, 'highlight'); } async autoSave() { const { xml } = await this.modeler.saveXML(); console.log('✓ 自动保存成功'); fetch('/api/process/save', { method: 'POST', body: JSON.stringify({ bpmn: xml }) }); } showContextMenu(event, element) { const menu = document.createElement('div'); menu.className = 'context-menu'; menu.innerHTML = ` <div onclick="editor.deleteElement()">删除</div> <div onclick="editor.duplicateElement()">复制</div> <div onclick="editor.showDocumentation()">文档</div> `; menu.style.left = event.event.pageX + 'px'; menu.style.top = event.event.pageY + 'px'; document.body.appendChild(menu); document.addEventListener('click', () => menu.remove()); } deleteElement() { const selected = this.modeler.get('selection').get(); if (selected.length > 0) { this.modeler.get('modeling').removeElements(selected); } } }
const editor = new ProcessEditor('#canvas');
|
| 事件 |
触发时机 |
常见用途 |
element.click |
用户点击元素 |
显示属性、高亮 |
element.dblclick |
用户双击 |
编辑元素 |
element.contextmenu |
右键菜单 |
自定义菜单 |
element.hover |
鼠标悬停 |
获取焦点 |
element.out |
鼠标离开 |
还原焦点 |
selection.changed |
选中改变 |
更新UI |
shape.added |
元素添加 |
记录日志、初始化 |
shape.removed |
元素删除 |
清理资源 |
connection.created |
连线创建 |
验证、标记 |
element.changed |
属性变更 |
实时保存 |
businessObject.updated |
特定属性变更 |
监听特定属性变更 |
execute |
任何操作 |
自动保存、刷新 |
小结
掌握事件系统:
- ✓ 理解EventBus的工作原理
- ✓ 监听常见交互事件
- ✓ 使用事件触发自定义逻辑
- ✓ 构建反应性的编辑界面
- ✓ 实现自动保存与验证
便能构建智能、响应迅速的流程编辑工具。
参考资料