Bpmn.js 进阶指南之右键菜单

简介: Bpmn.js 进阶指南之右键菜单

前言


🚀🚀现在开始第 12 小节,如何配置一个右键菜单


12. 扩展右键菜单


有的小伙伴这样的有需求:需要用户右键的时候有弹出框,用来取代原有上下文菜单 ContentPad ,以改变当前元素类型或者创建新的元素,这里为大家提供两种实现的方案。


12.1 完全自定义的右键菜单


第一步:阻止默认事件


为了组织默认的浏览器右键事件,不管哪种方式都需要第一步:阻止默认事件。


document.body.addEventListener('contextmenu', function (ev) {
    ev.preventDefault()
})


这里为什么不在 modeler.on(eventName, callback(event)) 的回调函数中调用 event.preventDefault(),主要是因为原生的插件模块

ElementTemplateChooser 会生成一个遮罩层插入到 body 元素中,在回调内阻止默认事件无法全部阻止成功。当然这里可以按照实际情况具体确认该监听函数添加到哪个元素上。


第二步:创建一个弹出框组件


这里使用的是 Naive UIPopover 组件,采用手动定位的形式。


<template>
  <n-popover
    :show="showPopover"
    :x="x"
    :y="y"
    :show-arrow="false"
    trigger="manual"
    placement="right-start"
  >
    <div @click.stop>测试右键菜单</div>
  </n-popover>
</template>
<script lang="ts">
  import { defineComponent, onMounted, ref } from 'vue'
  import EventEmitter from '@/utils/EventEmitter'
  export default defineComponent({
    name: 'ContextMenu',
    setup() {
      const showPopover = ref(false)
      const x = ref(0)
      const y = ref(0)
      onMounted(() => {
        EventEmitter.on('show-contextmenu', (event: MouseEvent) => {
          x.value = event.clientX
          y.value = event.clientY
          showPopover.value = true
        })
        // 手动隐藏 (注意 模板中的 click.stop)
        document.body.addEventListener('click', () => (showPopover.value = false))
      })
      return {
        showPopover,
        x,
        y
      }
    }
  })
</script>


这里使用的是 EventEmitter 事件订阅来触发显示,也可以创建一个显示方法,在父组件调用。


第三步:配置监听事件回调函数


// EnhancementContextmenu.ts
export default function (modeler: Modeler) {
  modeler.on('element.contextmenu', 2000, (event) => {
    const { element, originalEvent } = event
    EventEmitter.emit('show-contextmenu', originalEvent)
  })
}


这里将函数抽离成了一个 hook 方法,因为笔者在这里有其他逻辑,如果大家只是需要该事件来触发显示的话,可以直接将这部分代码放置在 new Modeler() 之后。


这个 element.contextmenu 主要包含以下属性:


  1. element: 当前右键的元素


  1. gfx: 该元素对应的 svg 元素节点


  1. originalEvent: 浏览器原生的右键事件实例


  1. type: 事件类型,一般是监听的事件的类型字符串,但是打印出来经常是 undefined


扩展:区分右键事件的触发对象来替换元素或者创建元素


第三步我们知道了 element.contextmenu 事件的回调函数参数有哪些值,那如何判断当前显示的弹出框内容呢?


根据原生的绘图逻辑和规则,在泳道和流程根节点中触发事件时,应该是创建新的流程元素节点的,而其他时候则应该是更改元素类型(这个看具体情况,有可能泳道、子流程也需要更改当前元素类型)。


  1. 创建新元素:这里与 Palettedragstart 事件类似,可以通过 ElementFactoryCreate 来实现


  1. 更改元素类型:可以使用 BpmnReplace.replaceElement(element, target, hints?) 来实现


当前,当触发的时候更改元素类型的时候,需要根据当前元素的类型进行判断,也可以根据业务需求改成其他类型的元素。


12.2 使用原生的 PopupMenu


这里根据第 11 小节,根据是否引用了 ElementTemplateChooser 模块也有两种情况。


import Modeler from 'bpmn-js/lib/Modeler'
import PopupMenu from 'diagram-js/lib/features/popup-menu/PopupMenu'
import { Base } from 'diagram-js/lib/model'
import Canvas, { Position } from 'diagram-js/lib/core/Canvas'
import editor from '@/store/editor'
import ContextPad from 'diagram-js/lib/features/context-pad/ContextPad'
import EventEmitter from '@/utils/EventEmitter'
import { isAppendAction } from '@/utils/BpmnDesignerUtils'
export default function (modeler: Modeler) {
    const config = editor().getEditorConfig
    if (!config.contextmenu) return
    modeler.on('element.contextmenu', 2000, (event) => {
        const { element, originalEvent } = event
        // 原生面板扩展
        // 1. 更改元素类型
        if (!isAppendAction(element)) {
            return config.templateChooser
                ? openEnhancementPopupMenu(modeler, element, originalEvent)
                : openPopupMenu(modeler, element, originalEvent)
        }
        // 2. 创建新元素 (仅开始模板扩展时可以)
        if (!config.templateChooser) return
        const connectorsExtension: any = modeler.get('connectorsExtension')
        connectorsExtension &&
        connectorsExtension.createAnything(originalEvent, getContextMenuPosition(originalEvent))
    })
}
// default replace popupMenu
function openPopupMenu(modeler: Modeler, element: Base, event: MouseEvent) {
    const contextPad = modeler.get<ContextPad>('contextPad')
    const popupMenu = modeler.get<PopupMenu>('popupMenu')
    if (popupMenu && !popupMenu.isEmpty(element, 'bpmn-replace')) {
        popupMenu.open(element, 'bpmn-replace', {
            cursor: { x: event.clientX + 10, y: event.clientY + 10 }
        })
        // 设置画布点击清除事件
        const canvas = modeler.get<Canvas>('canvas')
        const container = canvas.getContainer()
        const closePopupMenu = (ev) => {
            if (popupMenu && popupMenu.isOpen() && ev.delegateTarget.tagName === 'svg') {
                popupMenu.close()
                container.removeEventListener('click', closePopupMenu)
            }
        }
        container.addEventListener('click', closePopupMenu)
    }
}
// templateChooser enhancement replace popupMenu
function openEnhancementPopupMenu(modeler: Modeler, element: Base, event: MouseEvent) {
    const replaceMenu: any = modeler.get('replaceMenu')
    if (replaceMenu) {
        replaceMenu.open(element, getContextMenuPosition(event, true))
    }
}
///// utils
function getContextMenuPosition(event: MouseEvent, offset?: boolean): Position {
    return {
        x: event.clientX + (offset ? 10 : 0),
        y: event.clientY + (offset ? 25 : 0)
    }
}


实现效果如下:


网络异常,图片无法展示
|


网络异常,图片无法展示
|


网络异常,图片无法展示
|


这里的定位逻辑需要优化,篇幅有限暂时不做更新


使用基于 ElementTemplateChooser 模块的方式来实现右键菜单需要注意一个问题:该模块产生的 DOM 节点是直接插入到 body 节点下的,如果需要使用该方式的话,记得在最外层添加以下 css 代码,用来重置鼠标事件。但是这样会导致正常的点击事件无法关闭 ContextMenu 面板,所以建议修改遮罩层样式,以提示用户关闭


.cmd-change-menu {
  pointer-events: none !important;
  .cmd-change-menu__overlay {
    pointer-events: auto;
  }
}
.cmd-change-menu {
  background-color: rgba(0, 0, 0, .3);
}


后语


最近也在掘进看到了很多关于 bpmn.js 和 logicFlow 的如何选型或者两者的比较的文章,个人感觉两个库其实都是十分优秀的。logicFlow 在绘图方面,确实更加易于上手,api 和文档也更加友好。但是,如果针对配合后端流程引擎这一点来说的话,bpmn.js 的专业性就强了不少。


当然,bpmn.js 的上手难度确实要高不少,但是它本身的代码设计与功能拆分其实还是很友好的,只是需要静下心深入了解源码才行。


附上个人的小项目,基于 Vite + TypeScript+ Vue3 + NaiveUI + Bpmn.js 的流程编辑器(前端部分)vite-vue-bpmn-process


目录
相关文章
|
JavaScript
js更改网页默认右键菜单
js更改网页默认右键菜单
234 0
|
JavaScript 前端开发
JavaScript 自定义html元素鼠标右键菜单
JavaScript 自定义html元素鼠标右键菜单
326 0
|
JavaScript 前端开发
如使用原生js自定义右键菜单
如使用原生js自定义右键菜单
465 0
|
JavaScript 前端开发
js实现仿桌面右键,出现右键菜单功能
javascript有右键事件,可以利用这个事件,实现右键菜单功能。 喜欢点个赞。转发备注出处。
4957 0
js实现仿桌面右键,出现右键菜单功能
|
JavaScript
js禁止右键菜单代码、禁止复制粘贴代码
js禁止右键菜单代码、禁止复制粘贴代码
|
JavaScript
js实现浏览器右键菜单,屏蔽默认菜单
撤回消息 function bindMouseEvent(el){ var args = [].
1189 0
|
JavaScript 前端开发 内存技术
关于FlexPaper 2.1.2版本 二次开发 Logo 、打印、搜索、缩略图、添加按钮、js交互、右键菜单等相关问题
原文:关于FlexPaper 2.1.2版本 二次开发 Logo 、打印、搜索、缩略图、添加按钮、js交互、右键菜单等相关问题 先废话几句。最近用到文档在线浏览功能,之前用的是print2flash(一个工具,文档直接转flash,自带翻页搜索等一系列功能),由于无法与js进行交互,所以改用flexpaper。
1258 0
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
308 2