自定义bpmn.js上下文元素contextPad
一、回顾
contextPad
就是元素追加的上下文,可以直接在画板上的元素上添加其他元素。
在前面我们自定义了palette
和render
模块后,左侧工具栏和画板上的元素已经变成自定义的样式,但是contextPad
这部分还没有变化。同样这里也需要重写contextPad
的方法来覆盖默认方法。
二、文件结构
在plugins
文件目录下新建context-pad
文件夹。创建一个index.js
入口文件和contextPadProvider.js
自定义contextPad
文件(用来覆盖默认contextPad)。
三、代码实现
1. contextPadProvider.js文件
import {
assign,
forEach,
isArray
} from "min-dash";
import {
is
} from "bpmn-js/lib/util/ModelUtil";
import {
isAny
} from "bpmn-js/lib/features/modeling/util/ModelingUtil";
import {
hasPrimaryModifier
} from "diagram-js/lib/util/Mouse";
export default function ContextPadProvider(
config,
injector,
eventBus,
contextPad,
modeling,
elementFactory,
connect,
create,
popupMenu,
canvas,
rules,
translate,
elementRegistry
) {
config = config || {};
contextPad.registerProvider(this);
this._contextPad = contextPad;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._connect = connect;
this._create = create;
this._popupMenu = popupMenu;
this._canvas = canvas;
this._rules = rules;
this._translate = translate;
if (config.autoPlace !== false) {
this._autoPlace = injector.get("autoPlace", false);
}
eventBus.on("create.end", 250, function (event) {
var context = event.context,
shape = context.shape;
if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
return;
}
var entries = contextPad.getEntries(shape);
if (entries.replace) {
entries.replace.action.click(event, shape);
}
});
}
ContextPadProvider.$inject = [
"config.contextPad",
"injector",
"eventBus",
"contextPad",
"modeling",
"elementFactory",
"connect",
"create",
"popupMenu",
"canvas",
"rules",
"translate",
"elementRegistry"
];
ContextPadProvider.prototype.getContextPadEntries = function (element) {
var contextPad = this._contextPad,
modeling = this._modeling,
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create,
popupMenu = this._popupMenu,
canvas = this._canvas,
rules = this._rules,
autoPlace = this._autoPlace,
translate = this._translate;
var actions = {};
if (element.type === "label") {
return actions;
}
var businessObject = element.businessObject;
function startConnect(event, element) {
connect.start(event, element);
}
function removeElement() {
modeling.removeElements([element]);
}
/**
* Create an append action
*
* @param {string} type
* @param {string} className
* @param {string} [title]
* @param {Object} [options]
* @return {Object} descriptor
*/
function appendAction(type, className, title, options) {
if (typeof title !== "string") {
options = title;
title = translate("Append {type}", {
type: type.replace(/^bpmn:/, "")
});
}
function appendStart(event, element) {
var shape = elementFactory.createShape(assign({
type: type
}, options));
create.start(event, shape, {
source: element
});
}
var append = autoPlace ?
function (event, element) {
var shape = elementFactory.createShape(assign({
type: type
}, options));
autoPlace.append(element, shape);
} :
appendStart;
return {
group: "model",
className,
title,
action: {
dragstart: appendStart,
click: append
}
};
}
if (is(businessObject, "bpmn:FlowNode")) {
if (
!is(businessObject, "bpmn:EndEvent")
) {
assign(actions, {
"append.append-task": appendAction("bpmn:UserTask", "icon-custom taskNode", translate("Append Task")),
"append.gateway": appendAction("bpmn:ExclusiveGateway", "icon-custom gatewayNode", translate("Append Gateway")),
"append.end-event": appendAction("bpmn:EndEvent", "icon-custom endNode", translate("Append EndEvent")),
});
}
}
if (isAny(businessObject, ["bpmn:FlowNode", "bpmn:InteractionNode", "bpmn:DataObjectReference", "bpmn:DataStoreReference"])) {
assign(actions, {
connect: {
group: "edit",
// className: "bpmn-icon-connection-multi",
className: "feelec feel-lianxian",
title: translate("Connect using " + (businessObject.isForCompensation ? "" : "Sequence/MessageFlow or ") + "Association"),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
// delete element entry, only show if allowed by rules
var deleteAllowed = rules.allowed("elements.delete", {
elements: [element]
});
if (isArray(deleteAllowed)) {
// was the element returned as a deletion candidate?
deleteAllowed = deleteAllowed[0] === element;
}
if (deleteAllowed) {
assign(actions, {
delete: {
group: "edit",
className: "bpmn-icon-trash",
title: translate("Remove"),
action: {
click: removeElement
}
}
});
}
return actions;
};
代码思路解析:
重写 ContextPadProvider
类,同时覆盖了其原型上的 getContextPadEntries
方法
getContextPadEntries
方法返回一个对象,和 PaletteProvider
一样,返回的是需要的元素。
不同的是 contextPad
通过判断元素的类型来决定追加哪些元素。比如在结束节点 EndEvent
就没有其他元素,只有一个删除节点,而其他节点一样,所以就需要在 EndEvent
这个节点单独判断。
2. inedx.js文件
import CustomContextPadProvider from "./contextPadProvider";
export default {
__init__: ["contextPadProvider"],
contextPadProvider: ["type", CustomContextPadProvider]
};
3.css文件
自定义样式文件process-panel.scss
里再加入样式 contextPad 原生hover样式。
.djs-context-pad{
& .startNode.entry:hover {
background: url('../../../../../../public/bpmn_imgs/startNode.png') center no-repeat !important;
background-size: cover !important;
}
& .endNode.entry:hover {
background: url('../../../../../../public/bpmn_imgs/endNode.png') center no-repeat !important;
background-size: cover !important;
}
& .taskNode.entry:hover {
background: url('../../../../../../public/bpmn_imgs/taskNode.png') center no-repeat !important;
background-size: cover !important;
}
& .gatewayNode.entry:hover {
background: url('../../../../../../public/bpmn_imgs/gatewayNode.png') center no-repeat !important;
background-size: cover !important;
}
& .entry {
box-sizing: border-box;
background-size: 100%;
transition: all 0.3s;
}
& .entry:hover {
border: 1px solid #1890ff;
}
}
四、整合使用
到这里需要自定义样式的这三部分就完成了,最后的使用就在初始化BpmnModeler
时添加这三个自定义模块。
customBpmn.vue
文件
<template>
<div>
<div ref="bpmn-canvas">
</div>
</template>
<script>
import BpmnModeler from "bpmn-js/lib/Modeler";
// 自定义左侧菜单(修改 默认任务 为 用户任务)
import CustomPaletteProvider from "../package/process-designer/plugins/palette";
// 自定义渲染
import CustomRenderer from "../package/process-designer/plugins/render";
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
import CustomContextPadProvider from "../package/process-designer/plugins/context-pad";
export default {
data() {
return {
bpmnModeler: null,
};
},
mounted() {
this.initBpmnModeler();
},
methods: {
initBpmnModeler() {
if (this.bpmnModeler) return;
this.bpmnModeler = new BpmnModeler({
container: this.$refs.bpmn - canvas,
additionalModules: [
CustomPaletteProvider,
CustomRenderer,
CustomContextPadProvider,
], //添加自定义模块
});
},
},
};
</script>
五、效果展示
最后这三部分都变成自定义的样式了,要修改样式或者逻辑就在对应的文件修改,灰常Nice!!!