运维咖啡吧

追求技术的道路上,我从不曾停下脚步

最好用的流程编辑器bpmn-js系列之Palette

最好用的流程编辑器bpmn-js系列文章

前边写了四篇文章介绍了bpmn.js的基本使用,最近陆续有小伙伴加我催更,感谢对我这个半吊子前端的信任,接着更新bpmn.js的一些高级用法,本篇介绍对左侧工具栏Palette的隐藏和自定义修改

隐藏shape

左侧工具栏Palette有些图标我用不到,那该如何隐藏呢?最简单的方法就是直接找到对应的class,通过css隐藏,例如我不需要创建数据存储,可以通过下边的代码隐藏

.bpmn-icon-data-store {
  display: none;
}

自定义shape

为了编辑方便,我想在palette上添加一个shape该如何操作呢?这里我们需要自定义Palette,自定义Palette有两种方式可以选择,第一种就是基于默认的Palette来修改,第二种就是完全写个新的Palette来替代默认的Palette,第一种只能在默认的Palette上添加shape,而不能修改或删除,比较鸡肋我们就直接放弃了,来看下完全自定义Palette该如何实现

以下代码基于我们之前搭建好的代码框架,具体可看文章『最好用的流程编辑器bpmn-js系列之基本使用』

1.在components目录下新建customBpmn目录,在customBpmn目录下新建custom目录,每层目录下都新建index.js文件,最终目录结构如下

2.在custom目录下新建CustomPalette.js文件,内容如下

import { assign } from "min-dash";

export default function PaletteProvider(
  palette,
  create,
  elementFactory,
  handTool,
  lassoTool,
  spaceTool,
  globalConnect,
  translate
) {
  this.create = create;
  this.elementFactory = elementFactory;
  this.handTool = handTool;
  this.lassoTool = lassoTool;
  this.spaceTool = spaceTool;
  this.globalConnect = globalConnect;
  this.translate = translate;

  palette.registerProvider(this);
}

PaletteProvider.$inject = [
  "palette",
  "create",
  "elementFactory",
  "handTool",
  "lassoTool",
  "spaceTool",
  "globalConnect",
  "translate"
];

PaletteProvider.prototype.getPaletteEntries = function (element) {
  const {
    create,
    elementFactory,
    handTool,
    lassoTool,
    spaceTool,
    globalConnect,
    translate
  } = this;

  function createAction(type, group, className, title, options) {
    function createListener(event) {
      var shape = elementFactory.createShape(assign({ type: type }, options));

      if (options) {
        shape.businessObject.di.isExpanded = options.isExpanded;
      }

      create.start(event, shape);
    }

    var shortType = type.replace(/^bpmn:/, "");

    return {
      group: group,
      className: className,
      title: title || translate("Create {type}", { type: shortType }),
      action: {
        dragstart: createListener,
        click: createListener
      }
    };
  }

  return {
    "lasso-tool": {
      group: "tools",
      className: "bpmn-icon-lasso-tool",
      title: "Activate the lasso tool",
      action: {
        click: function (event) {
          lassoTool.activateSelection(event);
        }
      }
    },

    "tool-separator": {
      group: "tools",
      separator: true
    },

    "create.start-event": createAction(
      "bpmn:StartEvent",
      "event",
      "bpmn-icon-start-event-none",
      "创建开始节点"
    ),
    "create.end-event": createAction(
      "bpmn:EndEvent",
      "event",
      "bpmn-icon-end-event-none",
      "创建结束节点"
    ),
    "create.user-task": createAction(
      "bpmn:UserTask",
      "activity",
      "bpmn-icon-user-task",
      "创建用户任务"
    ),
    "create.exclusive-gateway": createAction(
      "bpmn:ExclusiveGateway",
      "gateway",
      "bpmn-icon-gateway-xor",
      "创建排他网关"
    )
  };
};

这段代码的意思相信各位前端的大佬比我理解的要深刻,就不过多介绍了,Platte展示的shape就是最后return输出的那个字典数据定义的,一个shape对应的数据格式如下:

"lasso-tool": {
  group: "tools",
  className: "bpmn-icon-lasso-tool",
  title: "Activate the lasso tool",
  action: {
    click: function (event) {
      lassoTool.activateSelection(event);
    }
  }
}

其中key为这个shape的名称,value为这个shape定义的一些属性,主要有四个:

通过这个数据我们就可以随意添加、删除或者修改Palette的shape了,改位置该样式轻松自如

3.在custom/index.js文件中添加如下内容将自定义的Palette导出

import CustomPalette from "./CustomPalette";

export default {
  __init__: ["paletteProvider"],
  paletteProvider: ["type", CustomPalette],
};

4.在customModeler/index.js文件中编写自定义的CustomModeler

import inherits from "inherits";

import Modeler from "bpmn-js/lib/Modeler";

import CustomModule from "./custom";

function CustomModeler(options) {
  Modeler.call(this, options);

  this._customElements = [];
}

inherits(CustomModeler, Modeler);

CustomModeler.prototype._modules = [].concat(CustomModeler.prototype._modules, [
  CustomModule
]);

export { CustomModeler };

5.在页面上引用自定义的CustomModeler以替代原本引用的BpmnModeler类,这样就能用到我们自定义的Palette啦

import { xmlStr } from "../mock/xmlStrPreview";
import { CustomModeler } from "../components/customBpmn";

export default {
  ...
  methods: {
    init() {
      const canvas = this.$refs.canvas;
      this.bpmnModeler = new CustomModeler({
        container: canvas
      });
      this.createNewDiagram();
    },
    async createNewDiagram() {
      try {
        const result = await this.bpmnModeler.importXML(xmlStr);
        const { warnings } = result;
        console.log(warnings);
      } catch (err) {
        console.log(err.message, err.warnings);
      }
    }
  }
};

最终效果如下:

Shape类型

关于Shape总共有哪些类型,以及各自对应的属性都是什么,这个官方没有具体的文档给列出,我在使用的时候通常直接查看源码bpmn-js/lib/features/palette/PaletteProvider.jsbpmn-js/lib/features/context-pad/ContextPadProvider.js文件获取,对于部分类型,需要添加options选项

例如中间时间事件IntermediateThrowEvent所对应的属性为:

return {
  "create.timer-intermediate-event": createAction(
    "bpmn:IntermediateThrowEvent",
    "event",
    "bpmn-icon-intermediate-event-catch-timer",
    "Create IntermediateThrowEvent",
    { eventDefinitionType: "bpmn:TimerEventDefinition" }
  )
};

写在最后

接触bpmn-js不久,且第一次用VUE,边学边写,文章难免出错,各位多多包含。想要打造一个好用的适合自己的流程编辑器,需要了解的内容比较多,bpmn-js会分多篇文章来介绍,欢迎关注

部分小伙伴对流程编辑器不了解,或是对BPMN不了解,我搭建了个在线的Demo: https://bpmn.ops-coffee.cn,点击链接即可轻松体验,建议PC端打开效果更好