preface

Q: What is bPMn.js? 🤔 ️

Bpmn. js is a BPMN2.0 rendering toolkit and Web modeler that allows you to draw flowcharts in the front end.

Q: Why did I write this series? 🤔 ️

Due to the business needs of the company, bPMN. js is used in the project. However, as the developer of BPMN. js is a foreign friend, there are few textbooks and no detailed documents on this aspect in China. So a lot of ways to use a lot of pits have to find their own. After mulling it over, I decided to write a series of textbooks on it to help more bPMn.js users or developers who want to find a good way to draw flowcharts. It is also a kind of consolidation of their own.

Because it is a series of articles, so the update may be more frequent, if you accidentally brush and not what you need also please understand 😊.

Github address of all textbooks: bPMN-Chinese-Document

Properties- Panel

In the previous chapter, I mainly introduced how to expand on the basis of the original property-panel, but many friends will say that I dislike the original property bar style 😅… I’m a mature front end, I need to have my own ideas…

OK… I respect you… In this chapter, Lin Zaidai will teach you how to beautify our properties-panel😊.

By reading this chapter you can learn:

  • Modify the default style of the property bar
  • The customproperties-panel
  • Changing a Node Namelabelattribute
  • Changing the Node colorcolorattribute
  • Modify theeventThe node type
  • Modify theTaskTypes of nodes
  • Initialize theproperties-panelAnd set some default values

Modify the default style of the property bar

Let’s take a look at what we can achieve by changing the default style of the property bar 🤔️!

Crimson theme
Technology Blue theme
Geek black theme

As shown at 👆, you can customize the property bar with different theme colors to make it look better.

To change the style of the default property bar, simply open the console (Window: F12, Mac: Option + Command + I), examine the element, find the class of each element, and override its original property in the code.

Remember when we referenced the property-panel style in the project’s main.js?

// main.js

import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // Right toolbar style
Copy the code

Now let’s create a styles folder in the project along with a bpmn-properties-theme-red. CSS file that will be used to write the property bar styles we need to customize.

Then reference it in main.js, preferably after the original style:

// main.js

import 'bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css' // Right toolbar style
import './styles/bpmn-properties-theme-red.css' // Red theme
Copy the code

For example, now I want to change the font color at the head of the property bar:


Find this class by reviewing the elements and then modify it in bPMn-properties-theme-red.css:

.bpp-properties-header>.label {
    color: rgb(239.112.96);
    font-size: 16px;
}
Copy the code

Save and open the page again to see the effect.

Of course, I’m just showing you how to change the default styles, so I’m just using the simplest CSS. There’s a lot of room to expand, you can write in less or sass, you can do your own theme switching, etc. Hope to inspire you 😊…

If you want to steal will be lazy… Take Lin’s dull style directly…

Github address of 👆 case above:

The LinDaiDai/BPMN – vue – properties – a panel

The customproperties-panel

Sometimes you might want to customize a property bar instead of using the official property-Panel, which is also possible.

For example, I want to display different attribute configurations on the right side for different node types, and then update them synchronously to XML after editing.

The custom properties panel

The implementation principle is also mentioned in the previous bPMN.js tutorial – Properties, mainly using the updateProperties() method to modify the attributes on the element node.

Now let’s see how to encapsulate such a custom property bar 😊.

preparation

Since custom property bars can be a lot of code and can involve complex business components, I recommend that you take them out of the way you introduced them in BPMn.js and encapsulate them into a generic custom property bar component.

The component’sprops

Now that we’ve decided to pull it out as a component, what should the props of this component be set to?

(Props is the value that the parent component passes to the child component, in this case the parent element is where bPMN.js is introduced, and the child element is the custom property bar component.)

To clarify our requirements, we need to click on different elements to render different configurations, so we can pass in a single element as props.

However, later in the process of writing, I found that there are a lot of binding events are related to Modeler. If these binding events are completed in the parent component, it is against our will to remove the separate component 🤔️?

So here, I’m writing the entire Modeler as props. This makes it easier to bind events to modeler or element.

OK… With props in mind, let’s create a custom-properties-Panel folder in the Components folder and create a file called propertiesView.vue in it to write our custom property bar component.

We expect this component to be able to be used in HTML like this:

<div class="containers" ref="content">
    <div class="canvas" ref="canvas"></div>
    <properties-view v-if="bpmnModeler" :modeler="bpmnModeler"></properties-view>
</div>
Copy the code

(bpmnModeler is the Modeler object you create using New bpmnModeler)

Write custom property bar components

1. Component structure

Let’s start by building the infrastructure for this component:

<! --PropertiesView.vue-->
<template>
    <div class="custom-properties-panel"></div>
</template>
<script>
export default {
    name: 'PropertiesView'.props: {
        modeler: {
          type: Object.default: (a)= > ({})
        }
    },
    data () {
        return {
            selectedElements: [].// The set of elements currently selected
            element: null // The element currently clicked
        }
    },
    created () {
        this.init()
    },
    methods: {
       init () {} 
    }
}
</script>
<style scoped></style>
Copy the code

2. The componenthtmlcode

Let me add something to this component first:

<template>
  <div class="custom-properties-panel">
    <div class="empty" v-if="selectedElements.length<=0">Select an element</div>
    <div class="empty" v-else-if="selectedElements.length>1">Only one element can be selected</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>
Copy the code

As 👆, I added three properties, ID, name, customProps. At the same time, there is a selectedElements judgment.

This is because when we’re working with graphics, if you use command + left, It is possible to select multiple nodes, so you need to make a decision.

3. Componentsjscode

If you’ve read a lot of code written by Lin Nerdy, you’ll notice that I prefer to refer some of my initialization code to a function called init(), which is a personal coding habit.

Here, our initialization function does the following:

  • useselection.changedListen for selected elements;
  • useelement.changedListen for changed elements.
init () {
 const { modeler } = this // The modeler passed in by the parent component
  modeler.on('selection.changed', e => {
    this.selectedElements = e.newSelection // Array, possibly multiple
    this.element = e.newSelection[0] // Take the first one by default
  })
  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
    }
  })
}
Copy the code

Alternatively, we can write a common attribute update method to update an attribute on an element:

/** * Updates the element attribute *@param { Object } Attributes to update, such as {name: ", id: "} */
updateProperties(properties) {
  const { modeler, element } = this
  const modeling = modeler.get('modeling')
  modeling.updateProperties(element, properties)
}
Copy the code

Then add an @change event to the input or other control on the property bar to update element synchronously when the content in the control changes.

/** * Changes the event triggered by the control *@param { Object } The input Event *@param { String } The name of the property to be modified */
changeField (event, type) {
  const value = event.target.value
  let properties = {}
  properties[type] = value
  this.element[type] = value
  this.updateProperties(properties) // Call the property update method
}
Copy the code

4. Complete component code

Combine all the code from 👆 above:

<template>
  <div class="custom-properties-panel">
    <div class="empty" v-if="selectedElements.length<=0">Select an element</div>
    <div class="empty" v-else-if="selectedElements.length>1">Only one element can be selected</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: (a)= > ({})
    }
  },
  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
        }
      })
    },
    /** * Changes the event triggered by the control *@param { Object } The input Event *@param { String } The name of the property to be modified */
    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
      })
    },
    /** * Updates the element attribute *@param { Object } Attributes to update, such as {name: "} */
    updateProperties(properties) {
      const { modeler, element } = this
      const modeling = modeler.get('modeling')
      modeling.updateProperties(element, properties)
    }
  }
}
</script>

<style scoped>
/** More code is available on git, see the git link at the bottom
.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>
Copy the code

Changing a Node Namelabelattribute

In the example above, we showed how to modify the attribute of an element. If you want to modify the label of an element, one way is to modify the name attribute as above 👆, or to update it with the modeling. UpdateLabel method:

updateName(name) {
  const { modeler, element } = this
  const modeling = modeler.get('modeling')
  modeling.updateLabel(element, name)
  / / is equivalent to modeling. UpdateProperties (element, {name})
},
Copy the code

Changing the Node colorcolorattribute

How do you get the user to manually change the color of the node?

Set the fill

You can use the modeling.setcolor method.

Let’s say I add a line of property to my code:

<fieldset class="element-item">
    <label>The node color</label>
    <input type="color" :value="element.color" @change="(event) => changeField(event, 'color')" />
  </fieldset>
Copy the code

Then transform the following changeField methods:

/** * Changes the event triggered by the control *@param { Object } The input Event *@param { String } The name of the property to be modified */
changeField(event, type) {
  const value = event.target.value
  let properties = {}
  properties[type] = value
  if (type === 'color') { // If it is the color attribute
    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})},Copy the code

The setColor method accepts two attributes:

  • fill: Filling color of the node
  • stroke: The color and node of the node borderlabelThe color of the

Here I show you how to change the node’s fill color, which is called fill, and of course you can change the stroke, which looks like this:

Set the stroke

Interestingly, if you set both fill and stroke to color:

modeling.setColor(element, {
    fill: color,
    stroke: color
})
Copy the code

Then you can’t see the label… This is because stroke also changes the color of the label so that it becomes the same as fill.

Set fill and stroke

But you don’t usually set the border and the fill to the same color… There is no need to…

If you really want to solve this problem, here is a bad way to force the label style to change in the global CSS:

.djs-label {
    fill: # 000! important;
}
Copy the code

Modify theeventThe node type

In some cases, we may also need to change the node type in the custom properties bar, such as on the Start node, by clicking the contextPad spanner:


Implement this function. We need to use bpmnReplace replaceElement this method.

First let’s look at where the event property is placed.

I changed the type of the start node to MessageEventDefinition


It is on the corresponding element. The businessObject. EventDefinitions in the array, and if the StartEvent and EndEvent, the array as undefinded.

Let’s see how this function is implemented 😄.

First add a dropdown to the HTML to change the event node type:

<! --PropertiesView.vue-->
<template>
    <fieldset class="element-item" v-if="isEvent">
        <label>Example Modify the Event node type</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: 'default'.value: ' ' },
                { label: 'MessageEventDefinition'.value: 'bpmn:MessageEventDefinition' },
                { label: 'TimerEventDefinition'.value: 'bpmn:TimerEventDefinition' },
                { label: 'ConditionalEventDefinition'.value: 'bpmn:ConditionalEventDefinition'}].eventType: ' '}},methods: {
        verifyIsEvent (type) { // Check whether the type is event
            return type.includes('Event')
        },
        changeEventType (event) {}
    },
    computed: {
        isEvent() { // Determine whether the element type currently clicked is event
          const { element } = this
          return this.verifyIsEvent(element.type)
        }
    }
}
</script>
Copy the code

Ok, complete the basic code above 👆, the main logic is to change the value of the drop-down box:

changeEventType(event) { // Change the drop-down box
  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
  })
},
Copy the code

Now if you change the drop-down box, you can change eventDefinitionType, but there’s a problem, if you click on another node and then click back to the start node, the default value of the drop-down box won’t be correct, That is, we also need to get the eventDefinitionType value for the start node itself.

At this point, we can do this kind of initializing properties-panel in the Selection. changed listener event.

init () {
    modeler.on('selection.changed', e => {
        this.selectedElements = e.newSelection
        this.element = e.newSelection[0]
        console.log(this.element)
        this.setDefaultProperties() // Set some default values
      })
}
setDefaultProperties() {
  const { element } = this
  if (element) {
    const { type, businessObject } = element
    if (this.verifyIsEvent(type)) { // If the type is event
      // Get the default eventDefinitionType
      this.eventType = businessObject.eventDefinitions ? businessObject.eventDefinitions[0] ['$type'] : ' '}}}Copy the code

Modify theTaskTypes of nodes

We already know how to modify the event node. What about the Task node 🤔️?

It’s pretty much the same thing.


Also, let’s add a dropdown for Task attributes in the HTML:

<! --PropertiesView.vue-->
<template>
    <fieldset class="element-item" v-if="isTask">
        <label>Example Modify the Task node type</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() { // Determine whether the element type being clicked is task
          const { element } = this
          return this.verifyIsTask(element.type)
        }
    }
}
</script>
Copy the code

Then when changing the Task dropdown:

changeTaskType(event) {
  const { modeler, element } = this
  const value = event.target.value // The value selected in the current drop-down box
  const bpmnReplace = modeler.get('bpmnReplace')
  bpmnReplace.replaceElement(element, {
    type: value // Change type directly})}Copy the code

Initialize theproperties-panelAnd set some default values

When we set our own custom property bar, we may need to make different business logic judgment according to different node types, and set some default values of properties-panel, such as 👆, change the event type, at this time we can do 🤔️?

As with changing the Event type, we can do this in the selection. Changed listening event.

init () {
    modeler.on('selection.changed', e => {
        this.selectedElements = e.newSelection
        this.element = e.newSelection[0]
        console.log(this.element)
        this.setDefaultProperties() // Set some default values
      })
}
setDefaultProperties() {
  const { element } = this
  if (element) {
    // Here you can get all the properties of the currently clicked node
    const { type, businessObject } = element
    // doSomeThing}}Copy the code

In fact, it is the same as the initialization of modifying the event type introduced at 👆 above. However, I am afraid that some friends directly skipped the modification of the event type and did not see this part, so I will mention it separately.

For example, if we want to get the label from our Shape and synchronize it to the custom properties bar on the right, we can do this:

In setDefaultProperties we can get the element that we clicked on via this.element, and when we print out the element, we’ll see that label is actually the name property in businessObject, so we just need to do something:

element['name'] = businessObject.name
Copy the code

If you change the label on the graph or the name in the custom property bar, it will be synchronized. See the code in Github for details.

replaceThe type of

Above 👆 we introduced how Event and Task elements are converted to types. This example only demonstrates a few types, so where to look at all types 🤔️?

You can find the bpmn.js source here:

https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/replace/ReplaceOptions.js
Copy the code

You can even export the desired content directly into the code:

import { START_EVENT } from 'bpmn-js/lib/features/replace/ReplaceOptions.js'
Copy the code

After the language

Above 👆 textbook case code address: LinDaiDai/ bPMn-vue-properties-panel

By the end of this chapter, properties-panel is a general introduction. Whether you want to use the original properties-Panel or use a custom properties-panel, I believe you have mastered 😄…

In the follow-up Lin Dai may be based on bPMn.js source code to list some commonly used properties and methods, so that you better understand bPMn.js.

Soon to the New Year 🧨…

Code finished this chapter, Lin Dull also began to pack home 😄…

Happy New Year again ~ 🔥 🎆

Finally, if you are also interested in bPMn. js, you can join our bPMn. js communication group 👇👇👇, learn together and make progress together.

Follow Lin Daodi’s public account and choose “bPMn. js group” in the “Other” menu at 😊.

LinDaiDai public qr code.jpg

For the full catalogue of the series, please check here: “The most detailed BPMN.js textbook in the whole Web”.

Series related recommendations:

“The most detailed BPMN.JS textbook in the Whole Web – Basic Chapter”

The most detailed BPMN.JS textbook in the whole Web – Events

The most detailed BPMN.js textbook on the Whole web – Renderer

The most detailed BPMN.js textbook -contextPad

“The most detailed BPMN. js textbook – Customize Palette”

“The most detailed BPMN. js textbook in the whole web – Editing and Deleting nodes”

“The most detailed BPMN.js textbook in the whole Web – Packaging Components”

The most detailed bPMN.js textbook in the whole web – Properties

“The most detailed bPMN. js textbook -properties-panel (1)”;