全网最详bpmn.js教材-properties-panel篇(下)

前言

Q: bpmn.js是什么? 🤔️javascript

bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.css

Q: 我为何要写该系列的教材? 🤔️html

由于公司业务的须要于是要在项目中使用到bpmn.js,可是因为bpmn.js的开发者是国外友人, 所以国内对这方面的教材不多, 也没有详细的文档. 因此不少使用方式不少坑都得本身去找.在将其琢磨完以后, 决定写一系列关于它的教材来帮助更多bpmn.js的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是本身对其的一种巩固.前端

因为是系列的文章, 因此更新的可能会比较频繁, 您要是无心间刷到了且不是您所须要的还请谅解😊.vue

不求赞👍不求心❤️. 只但愿能对你有一点小小的帮助.java

Properties-panel篇(下)

在上一章节主要介绍了如何在原有properties-panel的基础上进行扩展, 可是有不少小伙伴就会说我太嫌弃原有属性栏的样式了 😅...我是一名成熟的前端了, 我要有本身的想法...git

OK... 我尊重你...这一章节霖呆呆就来教教你们怎样美化咱们的properties-panel😊.github

经过这一章节的阅读你能够学习到:web

  • 修改属性栏的默认样式
  • 自定义 properties-panel
  • 修改节点名称 label属性
  • 修改节点颜色 color属性
  • 修改 event节点类型
  • 修改 Task节点的类型
  • 初始化 properties-panel并设置一些默认值

修改属性栏的默认样式

先来看看咱们经过修改属性栏的默认样式能够实现什么样的效果🤔️吧!canvas

绯红主题
绯红主题
科技蓝主题
科技蓝主题
极客黑主题
极客黑主题

如上👆所示, 你能够给属性栏定制不一样的主题颜色, 来美化它本来的样子.

其实想要修改默认属性栏的样式, 很是简单, 只要打开控制台(Window: F12, Mac : option + command + i)经过审查元素, 找到各个元素的class, 而后在代码里覆盖它原有的属性就能够了.

还记得咱们以前在项目的main.js中引用了properties-panel的样式吗?

// main.js

import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式
复制代码

如今让咱们在项目中建立一个styles文件夹, 同时建立一个bpmn-properties-theme-red.css文件, 里面将用来编写咱们须要自定义修改的属性栏样式.

以后在main.js 中引用它, 最好是放在原有样式的后面:

// main.js

import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // 右边工具栏样式
import './styles/bpmn-properties-theme-red.css' // 绯红主题
复制代码

好比如今我想要修改一下属性栏头部的字体颜色:

经过审查元素找到这个类, 而后在bpmn-properties-theme-red.css中修改它:

.bpp-properties-header>.label {
    color: rgb(239, 112, 96);
    font-size: 16px;
}
复制代码

保存再次打开页面就能够看到效果了.

固然我这里只是演示一下能够怎样去修改默认的样式, 因此只是用了最简单的css来演示. 这里其实有很大的扩展空间, 你能够用less或者sass来编写, 也能够本身实现一下主题切换等等的功能. 抛砖引玉但愿能给你启发 😊...

若是你想偷会懒...直接取霖呆呆的样式也行...

上面👆案例的github地址:

自定义properties-panel

有时候你可能不知足用官方提供的properties-panel, 而是想要自定义一个属性栏, 这也是能够实现的.

好比我想要根据不一样的节点类型, 在右边显示不一样的属性配置, 而且编辑完以后能够同步更新到xml上.

自定义properties-panel
自定义properties-panel

其实实现的原理在以前的 《全网最详bpmn.js教材-properties篇》中也有提过了, 主要是利用updateProperties()这个方法来修改元素节点上的属性.

如今就让咱们来看看如何封装一个这样的自定义属性栏吧😊.

前期准备

因为自定义属性栏的代码可能会不少, 并且可能还会涉及到不少复杂的业务组件, 因此我建议你将其从引入bpmn.js的地方给抽离出来, 也就是封装成一个通用的自定义属性栏组件.

组件的props

既然决定将其抽离成组件了, 那么这个组件的props应该设置成什么呢?

(props即父组件向子组件传递的值, 在这里父元素就是引入bpmn.js的地方, 子元素为自定义属性栏组件)

先来让咱们理理咱们的需求, 咱们须要点击不一样的元素来呈现不一样的配置, 那么能够将单个element做为props传递进去.

不事后来在编写的过程当中, 我发现有不少事件的绑定都是要涉及到modeler的, 如果将这些绑定事件都在父组件中完成不就违背了咱们抽离出单独组件的意愿了吗🤔️?

因此在这里, 我是将整个modeler做为props来编写.这样不论是给modeler绑定事件仍是给element绑定事件都很好作了.

OK...考虑好props, 让咱们在components文件夹下建立一个custom-properties-panel的文件夹, 并在其中建立一个名为PropertiesView.vue的文件, 用来编写咱们的自定义属性栏组件.

咱们指望的这个组件是可以这样在html中使用:

<div class="containers" ref="content">
    <div class="canvas" ref="canvas"></div>
    <properties-view v-if="bpmnModeler" :modeler="bpmnModeler"></properties-view>
</div>
复制代码

(bpmnModeler是你使用new BpmnModeler建立的modeler对象)

编写自定义属性栏组件

1. 组件结构

先来将这个组件的基础结构给搭好:

<!--PropertiesView.vue-->
<template>
    <div class="custom-properties-panel"></div>
</template>
<script> export default { name: 'PropertiesView', props: { modeler: { type: Object, default: () => ({}) } }, data () { return { selectedElements: [], // 当前选择的元素集合 element: null // 当前点击的元素 } }, created () { this.init() }, methods: { init () {} } } </script>
<style scoped></style>
复制代码

2. 组件html代码

先让我给这个组件里添加点东西:

<template>
  <div class="custom-properties-panel">
    <div class="empty" v-if="selectedElements.length<=0">请选择一个元素</div>
    <div class="empty" v-else-if="selectedElements.length>1">只能选择一个元素</div>
    <div v-else>
      <fieldset class="element-item">
        <label>id</label>
        <span>{{ element.id }}</span>
      </fieldset>
      <fieldset class="element-item">
        <label>name</label>
        <input :value="element.name" @change="(event) => changeField(event, 'name')" />
      </fieldset>
      <fieldset class="element-item">
        <label>customProps</label>
        <input :value="element.name" @change="(event) => changeField(event, 'customProps')" />
      </fieldset>
    </div>
  </div>
</template>
复制代码

如上👆, 我增长了三个属性, id, name, customProps. 同时, 有一个selectedElements的判断.

这是由于咱们在操做图形的时候, 若是你使用command + 左键(window上应该是Ctrl?)是能够选择多个节点的, 这时候就须要作一个判断.

3. 组件的js代码

若是你看多了霖呆呆写的代码, 你会发现我比较喜欢将一些初始化的代码提到一个叫作init()的函数中来, 这个是我的编码习惯哈...

在这里, 咱们的初始化函数主要作如下几件事:

  • 使用 selection.changed监听选中的元素;
  • 使用 element.changed监听发生改变的元素.
init () {
 const { modeler } = this // 父组件传递进来的 modeler
  modeler.on('selection.changed', e => {
    this.selectedElements = e.newSelection // 数组, 可能有多个
    this.element = e.newSelection[0] // 默认取第一个
  })
  modeler.on('element.changed', e => {
    const { element } = e
    const { element: currentElement } = this
    if (!currentElement) {
      return
    }
    // update panel, if currently selected element changed
    if (element.id === currentElement.id) {
      this.element = element
    }
  })
}
复制代码

另外, 咱们能够写一个公用的属性更新方法, 用来更新元素上的属性:

/** * 更新元素属性 * @param { Object } 要更新的属性, 例如 { name: '', id: '' } */
updateProperties(properties) {
  const { modeler, element } = this
  const modeling = modeler.get('modeling')
  modeling.updateProperties(element, properties)
}
复制代码

而后给属性栏上的input或者其它的控件, 增长一个@change事件, 当控件内的内容发生改变时, 同步更新element.

/** * 改变控件触发的事件 * @param { Object } input的Event * @param { String } 要修改的属性的名称 */
changeField (event, type) {
  const value = event.target.value
  let properties = {}
  properties[type] = value
  this.element[type] = value
  this.updateProperties(properties) // 调用属性更新方法
}
复制代码

4. 完整的组件代码

将上面👆的全部代码组合起来:

<template>
  <div class="custom-properties-panel">
    <div class="empty" v-if="selectedElements.length<=0">请选择一个元素</div>
    <div class="empty" v-else-if="selectedElements.length>1">只能选择一个元素</div>
    <div v-else>
      <fieldset class="element-item">
        <label>id</label>
        <span>{{ element.id }}</span>
      </fieldset>
      <fieldset class="element-item">
        <label>name</label>
        <input :value="element.name" @change="(event) => changeField(event, 'name')" />
      </fieldset>
      <fieldset class="element-item">
        <label>customProps</label>
        <input :value="element.name" @change="(event) => changeField(event, 'customProps')" />
      </fieldset>
    </div>
  </div>
</template>

<script> export default { name: 'PropertiesView', props: { modeler: { type: Object, default: () => ({}) } }, data() { return { selectedElements: [], element: null } }, created() { this.init() }, methods: { init() { const { modeler } = this modeler.on('selection.changed', e => { this.selectedElements = e.newSelection this.element = e.newSelection[0] }) modeler.on('element.changed', e => { const { element } = e const { element: currentElement } = this if (!currentElement) { return } // update panel, if currently selected element changed if (element.id === currentElement.id) { this.element = element } }) }, /** * 改变控件触发的事件 * @param { Object } input的Event * @param { String } 要修改的属性的名称 */ changeField(event, type) { const value = event.target.value let properties = {} properties[type] = value this.element[type] = value this.updateProperties(properties) }, updateName(name) { const { modeler, element } = this const modeling = modeler.get('modeling') // modeling.updateLabel(element, name) modeling.updateProperties(element, { name }) }, /** * 更新元素属性 * @param { Object } 要更新的属性, 例如 { name: '' } */ updateProperties(properties) { const { modeler, element } = this const modeling = modeler.get('modeling') modeling.updateProperties(element, properties) } } } </script>

<style scoped> /** 更多代码在git上有, git连接见底部后语 **/ .custom-properties-panel { position: absolute; right: 0; top: 0; width: 300px; background-color: #fff9f9; border-color: rgba(0, 0, 0, 0.09); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09); padding: 20px; } </style>
复制代码

修改节点名称label属性

在上面的例子中, 咱们演示了若是修改元素属性的, 若是你想要修改一个元素的label, 一种方式是像上面👆同样, 修改name这个属性, 或者用modeling.updateLabel这个方法更新也是同样的:

updateName(name) {
  const { modeler, element } = this
  const modeling = modeler.get('modeling')
  modeling.updateLabel(element, name)
  // 等同于 modeling.updateProperties(element, { name })
},
复制代码

修改节点颜色color属性

如何让用户手动修改节点的颜色呢?

设置fill
设置fill

能够利用modeling.setColor这个方法.

好比我在代码中添加一行属性:

<fieldset class="element-item">
    <label>节点颜色</label>
    <input type="color" :value="element.color" @change="(event) => changeField(event, 'color')" />
  </fieldset>
复制代码

而后改造如下changeField方法:

/** * 改变控件触发的事件 * @param { Object } input的Event * @param { String } 要修改的属性的名称 */
changeField(event, type) {
  const value = event.target.value
  let properties = {}
  properties[type] = value
  if (type === 'color') { // 如果color属性
    this.onChangeColor(value)
  }
  this.element[type] = value
  this.updateProperties(properties)
},
onChangeColor(color) {
  const { modeler, element } = this
  const modeling = this.modeler.get('modeling')
  modeling.setColor(element, {
    fill: color,
    stroke: null
  })
},
复制代码

setColor这个方法接收两个属性:

  • fill: 节点的填充色
  • stroke: 节点边框的颜色和节点 label的颜色

在上面我演示的是修改节点的填充色, 也就是fill, 固然你也能够改变stroke, 效果是这样的:

设置stroke
设置stroke

有意思的是, 若是你把fillstroke都设置成了color:

modeling.setColor(element, {
    fill: color,
    stroke: color
})
复制代码

那么label标签就看不到了... 这是由于stroke也会改变label的颜色, 让它变得和fill同样.

设置fill和stroke
设置fill和stroke

不过通常你也不会将边框和填充内容设置成一个色吧...不必...

若是你实在是想要解决这个问题的话, 这里有个不靠谱的作法, 就是在全局的css中, 将label的样式强行修改一下:

.djs-label {
    fill: #000!important;
}
复制代码

修改event节点类型

有些时候, 咱们可能还须要在自定义属性栏中修改这个节点的类型, 好比在开始节点, 点击contextPad上的小扳手:

实现这个功能咱们须要用到bpmnReplace.replaceElement这个方法.

首先让咱们看看event里这个属性是放在哪里的.

以下图: 我修改了一下开始节点的类型, 将它改成MessageEventDefinition

它对应是放在element.businessObject.eventDefinitions这个数组中的, 如果StartEventEndEvent, 则这个数组为undefinded.

让咱们来看看这个功能怎么实现哈 😄.

首先在html加上修改event节点类型的下拉框:

<!--PropertiesView.vue-->
<template>
    <fieldset class="element-item" v-if="isEvent">
        <label>修改event节点类型</label>
        <select @change="changeEventType" :value="eventType">
          <option v-for="option in eventTypes" :key="option.value" :value="option.value" >{{ option.label }}</option>
        </select>
  </fieldset>
</template>
<script> export default { data () { return { eventTypes: [ { label: '默认', value: '' }, { label: 'MessageEventDefinition', value: 'bpmn:MessageEventDefinition' }, { label: 'TimerEventDefinition', value: 'bpmn:TimerEventDefinition' }, { label: 'ConditionalEventDefinition', value: 'bpmn:ConditionalEventDefinition' } ], eventType: '' } }, methods: { verifyIsEvent (type) { // 判断类型是否是event return type.includes('Event') }, changeEventType (event) {} }, computed: { isEvent() { // 判断当前点击的element类型是否是event const { element } = this return this.verifyIsEvent(element.type) } } } </script>
复制代码

好了, 完成上面👆的基础代码, 主要逻辑就是在改变下拉框值的时候了:

changeEventType(event) { // 改变下拉框
  const { modeler, element } = this
  const value = event.target.value
  const bpmnReplace = modeler.get('bpmnReplace')
  this.eventType = value
  bpmnReplace.replaceElement(element, {
    type: element.businessObject.$type,
    eventDefinitionType: value
  })
},
复制代码

如今改变下拉框的值, 就能够改变eventDefinitionType的值了, 不过还有一个问题, 就是你点击了其它的节点, 而后再次点回开始节点的时候, 下拉框的默认值就不对了, 也就是说咱们还须要获取到这个开始节点自己的eventDefinitionType值.

这时候, 咱们能够在selection.changed监听事件中作这类初始化properties-panel的事情.

init () {
    modeler.on('selection.changed', e => {
        this.selectedElements = e.newSelection
        this.element = e.newSelection[0]
        console.log(this.element)
        this.setDefaultProperties() // 设置一些默认的值
      })
}
setDefaultProperties() {
  const { element } = this
  if (element) {
    const { type, businessObject } = element
    if (this.verifyIsEvent(type)) { // 如果event类型
      // 获取默认的 eventDefinitionType
      this.eventType = businessObject.eventDefinitions ? businessObject.eventDefinitions[0]['$type'] : ''
    }
  }
}
复制代码

修改Task节点的类型

event类型的节点咱们已经知道怎么修改了, 那么对于Task类型的节点呢 🤔️?

其实作法都差很少.

一样, 让咱们在html中加上针对Task类型的属性下拉框:

<!--PropertiesView.vue-->
<template>
    <fieldset class="element-item" v-if="isTask">
        <label>修改Task节点类型</label>
        <select @change="changeTaskType" :value="taskType">
          <option v-for="option in taskTypes" :key="option.value" :value="option.value" >{{ option.label }}</option>
        </select>
    </fieldset>
</template>
<script> export default { data () { return { taskTypes: [ { label: 'Task', value: 'bpmn:Task' }, { label: 'ServiceTask', value: 'bpmn:ServiceTask' }, { label: 'SendTask', value: 'bpmn:SendTask' }, { label: 'UserTask', value: 'bpmn:UserTask' } ], taskType: '' } }, methods: { verifyIsTask(type) { return type.includes('Task') }, changeTaskType (event) {} }, computed: { isTask() { // 判断当前点击的element类型是否是task const { element } = this return this.verifyIsTask(element.type) } } } </script>
复制代码

而后在改变Task下拉框的时候:

changeTaskType(event) {
  const { modeler, element } = this
  const value = event.target.value // 当前下拉框选择的值
  const bpmnReplace = modeler.get('bpmnReplace')
  bpmnReplace.replaceElement(element, {
    type: value // 直接修改type就能够了
  })
}
复制代码

初始化properties-panel并设置一些默认值

咱们在设置本身的自定义属性栏的时候, 可能要根据不一样的节点类型来作不一样的业务逻辑判断, 并对properties-panel作一些默认值的设置, 好比上面👆的修改event类型, 这时候咱们能够怎么样作呢 🤔️?

修改event类型同样, 咱们能够在selection.changed监听事件中完成这个功能.

init () {
    modeler.on('selection.changed', e => {
        this.selectedElements = e.newSelection
        this.element = e.newSelection[0]
        console.log(this.element)
        this.setDefaultProperties() // 设置一些默认的值
      })
}
setDefaultProperties() {
  const { element } = this
  if (element) {
    // 这里能够拿到当前点击的节点的全部属性
    const { type, businessObject } = element
    // doSomeThing
  }
}
复制代码

其实就是和上面👆介绍修改event类型的初始化同样, 不过我怕有的小伙伴直接跳过了修改event类型没有看到这一部分, 因此单独拎出来讲下.

replace的类型

在上面👆咱们介绍了关于EventTask类型的元素是如何转化类型的, 案例中也仅仅演示了几种类型, 那么所有的类型到哪里看呢 🤔️?

你能够在bpmn.js的源码这里找到:

https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/replace/ReplaceOptions.js
复制代码

你甚至能够直接到代码中将里面你要的内容导出:

import { START_EVENT } from 'bpmn-js/lib/features/replace/ReplaceOptions.js'
复制代码

后语

上面👆教材案例的代码地址: LinDaiDai/bpmn-vue-properties-panel

截止到本章节, properties-panel算是介绍大概了, 不论是要使用原有的properties-panel仍是使用自定义properties-panel我相信你都已经掌握了 😄...

在后续霖呆呆可能会根据bpmn.js源码来列举一些经常使用的属性和方法, 以便你更好的了解bpmn.js.

立刻要过年了🧨了...

码完了这一章节, 霖呆呆也要开始整理回家的行李了 😄 ...

再次祝你们新年快乐呀~ 🔥 🎆

最后, 若是你也对bpmn.js 感兴趣能够进咱们的bpmn.js交流群👇👇👇, 共同窗习, 共同进步.

关注霖呆呆的公众号, 选择“其它”菜单中的“bpmn.js群”便可😊.

LinDaiDai公众号二维码.jpg
LinDaiDai公众号二维码.jpg

系列所有目录请查看此处: 《全网最详bpmn.js教材》

系列相关推荐:

《全网最详bpmn.js教材-基础篇》

《全网最详bpmn.js教材-事件篇》

《全网最详bpmn.js教材-renderer篇》

《全网最详bpmn.js教材-contextPad篇》

《全网最详bpmn.js教材-自定义palette篇》

《全网最详bpmn.js教材-编辑、删除节点篇》

《全网最详bpmn.js教材-封装组件篇》

《全网最详bpmn.js教材-properties篇》

《全网最详bpmn.js教材-properties-panel篇(上)》 ;

相关文章
相关标签/搜索