胖蔡说技术
随便扯扯

聊一聊bpmn-js中的contextpad

bpmn-js 阅读指南:

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-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修改尺寸。

我当前定义的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插件中。

赞(0) 打赏
转载请附上原文出处链接:胖蔡说技术 » 聊一聊bpmn-js中的contextpad
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏