Q: bpmn.js是什么? 🤔️javascript
”
bpmn.js是一个BPMN2.0渲染工具包和web建模器, 使得画流程图的功能在前端来完成.css
Q: 我为何要写该系列的教材? 🤔️html
”
由于公司业务的须要于是要在项目中使用到bpmn.js
,可是因为bpmn.js
的开发者是国外友人, 所以国内对这方面的教材不多, 也没有详细的文档. 因此不少使用方式不少坑都得本身去找.在将其琢磨完以后, 决定写一系列关于它的教材来帮助更多bpmn.js
的使用者或者是期于找到一种好的绘制流程图的开发者. 同时也是本身对其的一种巩固.前端
因为是系列的文章, 因此更新的可能会比较频繁, 您要是无心间刷到了且不是您所须要的还请谅解😊.vue
不求赞👍不求心❤️. 只但愿能对你有一点小小的帮助.java
在上一章节主要介绍了如何在原有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
上.
其实实现的原理在以前的 《全网最详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
对象)
先来将这个组件的基础结构给搭好:
<!--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>
复制代码
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
?)是能够选择多个节点的, 这时候就须要作一个判断.
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) // 调用属性更新方法
}
复制代码
将上面👆的全部代码组合起来:
<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
属性如何让用户手动修改节点的颜色呢?
能够利用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
, 效果是这样的:
有意思的是, 若是你把fill
和stroke
都设置成了color
:
modeling.setColor(element, {
fill: color,
stroke: color
})
复制代码
那么label
标签就看不到了... 这是由于stroke
也会改变label
的颜色, 让它变得和fill
同样.
不过通常你也不会将边框和填充内容
设置成一个色吧...不必...
若是你实在是想要解决这个问题的话, 这里有个不靠谱的作法, 就是在全局的css
中, 将label
的样式强行修改一下:
.djs-label {
fill: #000!important;
}
复制代码
event
节点类型有些时候, 咱们可能还须要在自定义属性栏中修改这个节点的类型, 好比在开始节点, 点击contextPad
上的小扳手:
实现这个功能咱们须要用到bpmnReplace.replaceElement
这个方法.
首先让咱们看看event
里这个属性是放在哪里的.
以下图: 我修改了一下开始节点的类型, 将它改成MessageEventDefinition
它对应是放在element.businessObject.eventDefinitions
这个数组中的, 如果StartEvent
和EndEvent
, 则这个数组为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
的类型在上面👆咱们介绍了关于Event
和Task
类型的元素是如何转化类型的, 案例中也仅仅演示了几种类型, 那么所有的类型到哪里看呢 🤔️?
你能够在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群”便可😊.
系列所有目录请查看此处: 《全网最详bpmn.js教材》
系列相关推荐: