聊一聊bpmn-js中的contextpad

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


聊一聊bpmn-js中的contextpad


如上图,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-jsContextPad模块中提供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修改尺寸。

聊一聊bpmn-js中的contextpad

我当前定义的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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!