bpmn-js
内置提供了一个’contextPadprovider
‘右键面板,来协助我们快速创建和修改图形模块,其原理类似Palette方式,使用的是didi以插件方式来实现的动态或覆盖两种方式的创建。接下来我们就来快速了解下bpmn-js
中的contextPadprovider
已经如何对它进行修改定制操作。

如上图,contextPadprovider
就是右键元素显示的元素操作面板区域,contextPadprovider
通过当前选中元素的不同进行提示不同的操作元素显示,一辅助我们能进行更加快速的创建操作,降低非必要的左侧palette
拖拽操作,提高制图效率和用户体验度。
了解context-pad
在对context-pad
进行定制修改之前,我们先来了解下bpmn-js
源码中的context-pad
是什么样子的。如下我们了解几个比较重要的context-pad
配置。
1、插件注册参数
如下为插件的基础注册配置:
export default {
__depends__: [
AppendPreviewModule,
DirectEditingModule,
ContextPadModule,
SelectionModule,
ConnectModule,
CreateModule,
PopupMenuModule
],
__init__: [ 'contextPadProvider' ], // 注册插件名为:contextPadProvider
contextPadProvider: [ 'type', ContextPadProvider ]
};
2、插件核心方法
插件的使用类似与Palette中的模式,通过diagram-js代理全局的context-pad注册与生产,自定义的ContextPadProvider插件需要满足两个条件:注册、生成。
注册
diagram-js
的ContextPad
模块中提供registerProvider
方法供我们进行contextpad
提供器的注册操作,其代码如下:
// diagram-js/lib/features/context-pad/ContextPad.js
/**
*
* 提供支持contextpad的注册,并指定优先级
*
* @param {number} priority
* @param {ContextPadProvider} provider
*/
ContextPad.prototype.registerProvider = function(priority, provider) {
if (!provider) {
provider = priority;
priority = DEFAULT_PRIORITY;
}
this._eventBus.on('contextPad.getProviders', priority, function(event) {
event.providers.push(provider);
});
};
// 插件内使用
contextPad.registerProvider(this);
配置追加操作
ContextPadProvider
插件提供的核心方法就两个,分别用于单个元素选中操作:getContextPadEntries
,这也是最常用到的api
,和批量操作getMultiElementContextPadEntries
。两个方法均是返回一个map
集合用于显示追加元素。方法格式如下:
// 单个操作元素
getContextPadEntries?: (element: ElementType) => ContextPadEntriesCallback<ElementType> | ContextPadEntries<ElementType>;
// 多元素操作
getMultiElementContextPadEntries?: (elements: ElementType[]) => ContextPadEntriesCallback<ElementType> | ContextPadEntries<ElementType>;
其操作位于源码位置:diagram-js/lib/features/context-pad/ContextPad.js
ContextPad.prototype.getEntries = function(target) {
var providers = this._getProviders();
var provideFn = isArray(target)
? 'getMultiElementContextPadEntries'
: 'getContextPadEntries';
var entries = {};
// loop through all providers and their entries.
// group entries by id so that overriding an entry is possible
forEach(providers, function(provider) {
if (!isFunction(provider[provideFn])) {
return;
}
var entriesOrUpdater = provider[provideFn](target);
if (isFunction(entriesOrUpdater)) {
entries = entriesOrUpdater(entries);
} else {
forEach(entriesOrUpdater, function(entry, id) {
entries[id] = entry;
});
}
});
return entries;
};
自定义ContextPad
通过上述的了解,可以对contextpad
有个大致的了解,想要自定义contextpad
,只需要两个步骤:
-
通过
contextpad
注册提供器 -
实现
getContextPadEntries
方法(暂不考虑多选批量操作情况),返回操作元素
根据didi
插件机制的实现来分析,我们可以通过两种方式来实现我们的需求:追加contextPad
和重写覆盖。
追加方式
这里使用官方提供的示例:CustomContextPad.js,如下是代码:
const SUITABILITY_SCORE_HIGH = 100,
SUITABILITY_SCORE_AVERGE = 50,
SUITABILITY_SCORE_LOW = 25;
export default class CustomContextPad {
constructor(bpmnFactory, config, contextPad, create, elementFactory, injector, translate) {
this.bpmnFactory = bpmnFactory;
this.create = create;
this.elementFactory = elementFactory;
this.translate = translate;
if (config.autoPlace !== false) {
this.autoPlace = injector.get('autoPlace', false);
}
contextPad.registerProvider(this); // 注册
}
// 该方法提供当前element元素的contextpad配置,是一个对象格式
getContextPadEntries(element) {
const {
autoPlace,
bpmnFactory,
create,
elementFactory,
translate
} = this;
function appendServiceTask(suitabilityScore) {
return function(event, element) {
if (autoPlace) {
const businessObject = bpmnFactory.create('bpmn:Task');
businessObject.suitable = suitabilityScore;
const shape = elementFactory.createShape({
type: 'bpmn:Task',
businessObject: businessObject
});
autoPlace.append(element, shape);
} else {
appendServiceTaskStart(event, element);
}
};
}
function appendServiceTaskStart(suitabilityScore) {
return function(event) {
const businessObject = bpmnFactory.create('bpmn:Task');
businessObject.suitable = suitabilityScore;
const shape = elementFactory.createShape({
type: 'bpmn:Task',
businessObject: businessObject
});
create.start(event, shape, element);
};
}
return {
'append.low-task': {
group: 'model',
className: 'bpmn-icon-task red',
title: translate('Append Task with low suitability score'),
action: {
click: appendServiceTask(SUITABILITY_SCORE_LOW),
dragstart: appendServiceTaskStart(SUITABILITY_SCORE_LOW)
}
},
'append.average-task': {
group: 'model',
className: 'bpmn-icon-task yellow', // 可以通过指定的类名通过css设置颜色
title: translate('Append Task with average suitability score'),
action: {
click: appendServiceTask(SUITABILITY_SCORE_AVERGE),
dragstart: appendServiceTaskStart(SUITABILITY_SCORE_AVERGE)
}
},
'append.high-task': {
group: 'model',
className: 'bpmn-icon-task green',
title: translate('Append Task with high suitability score'),
action: {
click: appendServiceTask(SUITABILITY_SCORE_HIGH),
dragstart: appendServiceTaskStart(SUITABILITY_SCORE_HIGH)
}
}
};
}
}
// 需要依赖使用的插件
CustomContextPad.$inject = [
'bpmnFactory',
'config',
'contextPad',
'create',
'elementFactory',
'injector',
'translate'
];
// 导出定义index.js
export default {
__init__: [ 'customContextPad'],
customContextPad: [ 'type', CustomContextPad ],
};
通过上述插件定义和实现后,只需要在modeler
中加载就可以实现:
// 使用
import BpmnModeler from "bpmn-js/lib/Modeler";
import CustomContextPad from '../CustomContextPad'
const bpmnModeler = new BpmnModeler({
container: this.$refs["bpmn-canvas"],
additionalModules: [CustomContextPad ],
});
覆盖重写
覆盖重写和上述的追加方式唯一的不同就是插件的__init__
定义为contextPadProvider
,这样我们定义的插件就会覆盖bpmn-js
中的ContextpadProvider
插件。
// 导出定义index.js
export default {
__init__: [ 'contextPadProvider'],
contextPadProvider: [ 'type', CustomContextPad ],
};
几个知识点
1、getContextPadEntries(element)
返回数据格式
getContextPadEntries
接收参数为当前操作的元素,返回参数格式如下:
return {
'replace': { // 唯一key
group: 'edit', // 分组
className: 'bpmn-icon-screw-wrench', // 指定类名,这里可以用于配置预览图和预留配置自定义css
title: translate('Change type'), // hover上去显示的提示文字
action: { // 事件操作,常规处理dragstart和click就可以
click(event, element) {
console.log('get 修改类型的popup:', element)
const position = assign(getReplaceMenuPosition(element), {
cursor: { x: event.x, y: event.y },
})
popupMenu.open(element, 'bpmn-replace', position)
},
},
},
}
2、group有什么用?
contextpad
使用group
将追加元素进行分类,同一个model
的追加元素放在一起,每个model
都是由一个block
布局包裹,且内置将block的宽度固定死只能单行放三个元素,若希望改变大小,可以通过css
修改尺寸。
我当前定义的contextpad
中存在三种类型group:model、edit、connect
。若追加可将新增元素放置在已配置group
内,也可以单独定义一个新的group
,但需要考虑布局排版是否合适。
3、replace修改元素类型的配置在哪儿?
bpmn-js中给我们提供的contextpad中有一个edit的分类操作用于存放操作型功能,这里放了两个操作:
// 删除操作
if (this._isDeleteAllowed(elements)) {
assign(actions, {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: this._translate('Remove'),
action: {
click: function(event, elements) {
modeling.removeElements(elements.slice());
}
}
}
});
}
// 替换元素操作
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
// Replace menu entry
assign(actions, {
'replace': {
group: 'edit',
className: 'bpmn-icon-screw-wrench',
title: translate('Change type'),
action: {
click: function(event, element) {
var position = assign(getReplaceMenuPosition(element), {
cursor: { x: event.x, y: event.y }
});
popupMenu.open(element, 'bpmn-replace', position, {
title: translate('Change element'),
width: 300,
search: true
});
}
}
}
});
}
上述代码可知,修改元素的类型具体实现在popupMenu
插件中。
原文始发于微信公众号(胖蔡话前端):聊一聊bpmn-js中的contextpad
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/236152.html