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 😊.

Don’t ask for praise πŸ‘ don’t ask for heart ❀️. I only hope to have a little help for you.

Customize the Renderer chapter

Following on from the previous chapter, we already know how to customize the left Palette. If you don’t know, you can check it out in bPMN.js textbook – Customize the Palette.

But at the same time, we know that simply changing the Palette is not enough, because the Palette will still be “naked” :


In this section we will look at how to customize the graphics on the canvas, that is, to implement a custom Renderer.

By reading you can learn:

  • Modify the default Renderer
  • Fully custom Renderer
  • Label Specifies the label below the element

Modify the default Renderer

As with the custom Palette, take a look at the easiest Palette to modify on the original element.

preparation

Let’s move on to the LinDaiDai/ BPMN-VUe-Custom case project.

Create a custom renderer. Vue file in the Components folder and configure the route “Custom renderer”.

Create a new customRenderer. js file in the Components/Custom folder and customize the renderer.

Create a new utils folder under the Components folder and create a util.js file to hold some common methods and configurations.

writeCustomRenderer.vuecode

Since we are making changes to an existing bPMn.js element, we can first introduce the BaseRenderer class and let our custom renderer inherit it:

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer' // Introduce the default renderer
const HIGH_PRIORITY = 1500 // Highest priority
export default class CustomRenderer extends BaseRenderer { / / BaseRenderer inheritance
    constructor(eventBus, bpmnRenderer) {
        super(eventBus, HIGH_PRIORITY)
        this.bpmnRenderer = bpmnRenderer
    }

    canRender(element) {
        // ignore labels
        return! element.labelTarget } drawShape(parentNode, element) {// The core function is to draw the shape
        const shape = this.bpmnRenderer.drawShape(parentNode, element)
        return shape
    }

    getShapePath(shape) {
        return this.bpmnRenderer.getShapePath(shape)
    }
}

CustomRenderer.$inject = ['eventBus'.'bpmnRenderer']
Copy the code

The above πŸ‘† code is very simple, I believe we can see clearly.

Note: There is a small hole to pay attention to, isHIGH_PRIORITYCannot be removed, otherwise you will find that it will not perform the followingdrawShpefunction

Here may have some friends to ask, feel you did so much is not useful ah, still do not see the effect of the custom renderer πŸ˜…!

That’s right, it’s not enough to just do the above steps, the key is how to write drawShape.

writedrawShapecode

We can start by writing this code in the utils/util.js file we created earlier:

// util.js
const customElements = ['bpmn:Task']

export { customElements }
Copy the code

Create an array called customElements and export it. Why is there only one item in the array BPMN :Task? πŸ€” ️

That’s because the lindaidai-task type I created in the last case was BPMN :Task.

So the purpose of this array is to put the types that we need to customize so that we can distinguish them from elements that don’t need to be customized during rendering.

There are even some configurations you can do:

const customElements = ['bpmn:Task'] // Define the type of the element
const customConfig = { // Customize the configuration of elements (used later)
    'bpmn:Task': {
        'url': 'https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/rules.png'.'attr': { x: 0.y: 0.width: 48.height: 48}}}export { customElements, customConfig }
Copy the code

Let’s use and write it in customRenderer.js:

import { customElements, customConfig } from '.. /utils/util'. drawShape(parentNode, element) {const type = element.type // Get the type
      if (customElements.includes(type)) { // or customConfig[type]
        const { url, attr } = customConfig[type]
        const customIcon = svgCreate('image', { // Create an image here. attr,href: url
        })
        element['width'] = attr.width // I changed the width and height of the element directly
        element['height'] = attr.height
        svgAppend(parentNode, customIcon)
        return customIcon
      }
      const shape = this.bpmnRenderer.drawShape(parentNode, element)
      return shape
    }
...
Copy the code

As you can see, the way to make the page render the way it wants is to create an image using the svgCreate method and add it to the parent node.

Export and useCustomRenderer

Custom renderer also needs to be exported to use. Modify the custom/index.js file:

import CustomPalette from './CustomPalette'
import CustomRenderer from './CustomRenderer'

export default {
    __init__: ['customPalette'.'customRenderer'].customPalette: ['type', CustomPalette],
    customRenderer: ['type', CustomRenderer]
}
Copy the code

Note:__init__Attribute naming incustomRendererAll are fixed writing methods can not be modified, otherwise there will be no effect

If you looked at custom-palette. Vue, you can apply it directly to the page:

<! --custom-renderer.vue-->
<script>. import customModulefrom './custom'. this.bpmnModeler =new BpmnModeler({
...
    additionalModules: [
        // Left toolbar and node
        propertiesProviderModule,
        // A custom node
        customModule
    ]
})
Copy the code

Note: in the project case, I used thecustom-paletteIs introduced inImportJS/onlyRenderer.js, and the above case is introducedcustom/index.jsFor the sake of explanation, you have to figure out how to distinguish this.

Now open the page to see the effect, type isbpmn:TaskNodes are rendered as custom “golden blocks” 😝:

Fully custom Renderer

Fully customizing the Renderer means using new CustomModeler instead of using New BpmnModeler to create the canvas.

This part is explained in detail in the bPMN. js textbook – Custom Palette, so I will not elaborate too much.

Create a customRender. Js file in the customModeler/ Custom folder again and write the following code:

/* eslint-disable no-unused-vars */
import inherits from 'inherits'

import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer'

import {
    append as svgAppend,
    create as svgCreate
} from 'tiny-svg'

import { customElements, customConfig } from '.. /.. /utils/util'
/** * A renderer that knows how to render custom elements. */
export default function CustomRenderer(eventBus, styles) {
    BaseRenderer.call(this, eventBus, 2000)

    var computeStyle = styles.computeStyle

    this.drawCustomElements = function(parentNode, element) {
        console.log(element)
        const type = element.type // Get the type
        if (customElements.includes(type)) { // or customConfig[type]
            const { url, attr } = customConfig[type]
            const customIcon = svgCreate('image', {
                ...attr,
                href: url
            })
            element['width'] = attr.width // I changed the width and height of the element directly
            element['height'] = attr.height
            svgAppend(parentNode, customIcon)
            return customIcon
        }
        const shape = this.bpmnRenderer.drawShape(parentNode, element)
        return shape
    }
}

inherits(CustomRenderer, BaseRenderer)

CustomRenderer.$inject = ['eventBus'.'styles']

CustomRenderer.prototype.canRender = function(element) {
    // ignore labels
    return! element.labelTarget; } CustomRenderer.prototype.drawShape =function(p, element) {
    return this.drawCustomElements(p, element)
}

CustomRenderer.prototype.getShapePath = function(shape) {
    console.log(shape)
}
Copy the code

Simply modify the drawShape method in the prototype chain.

And then remember customModeler/custom/index. Js will be exported.

Label Specifies the label below the element

How do I customize the label tag below the element?

Therefore, I spent some time studying the label label.

First, the label label is actually an attribute named name on each label in XML, as shown below:


Both the start node and lindaidai-Task have a name attribute, but on BPMN :StartEvent the label is displayed because there is a bPMNdi :BPMNLabel label underneath.

The result is a graph that looks like this:

bpmn11.png

So how do we display the label here?

First of all, let’s start withShapePrint it out and see:

You can see that in businessObject there is a name attribute…

In this case, we can definitely get the name attribute in drawShape, and then we can use svgCreate to add a text label to the parent node.

// CustomRenderer.js

import { hasLabelElements } from '.. /.. /utils/util'

drawShape(parentNode, element) {
    const type = element.type // Get the type
    if (customElements.includes(type)) { // or customConfig[type]
        const { url, attr } = customConfig[type]
        const customIcon = svgCreate('image', {
            ...attr,
            href: url
        })
        element['width'] = attr.width // I changed the width and height of the element directly
        element['height'] = attr.height
        svgAppend(parentNode, customIcon)
        // Determine if there is a name attribute to render the label
        if(! hasLabelElements.includes(type) && element.businessObject.name) {const text = svgCreate('text', {
                x: attr.x,
                y: attr.y + attr.height + 20.// y takes the parent element y+height+20
                "font-size": "14"."fill": "# 000"
            })
            text.innerHTML = element.businessObject.name
            svgAppend(parentNode, text)
            console.log(text)
        }
        return customIcon
    }
    const shape = this.bpmnRenderer.drawShape(parentNode, element)
    return shape
}
Copy the code

Since some elements have a label attribute, such as BPMN :StartEvent, they don’t need to be re-rendered, so I add a hasLabelElements array to util.js:

// utils/util.js
const hasLabelElements = ['bpmn:StartEvent'.'bpmn:EndEvent'] // Start with the element type of the label
Copy the code

Element.allellabels. Length <=0 to filter out all elements that have labels in the first place, but I can’t get labels in the rendering phase, so the length is always 0.

Opening the page looks like this:

bpmn13.png

Looks like it worked! good boy ! πŸ˜„

But when I double-click to edit the label text, I get something like this:


It creates a new input box directly on top of my original shape…

The frontal πŸ˜…… I don’t really have a good solution, but here’s a solution that seems to work: When you double-click an element, either remove the text or set its innerHTML to “”.

Of course, if you feel that you can also watch it, we do not tamper with it, after all, you edit the content here, the label below will also change synchronously.

At the worst, you can change the style of the djs-direct-editing parent class globally, and override the following text. Of course it doesn’t feel like a very good idea. In app.css, write:

.djs-direct-editing-parent {
    top: 130px! important;
    width: 60px! important;
}
Copy the code

conclusion

SvgCreate (svgCreate, svgCreate, svgCreate, svgCreate, svgCreate, svgCreate, svgCreate, svgCreate)

Popular science wave good, ha ha πŸ˜„: basic knowledge of SVG

After the language

The above πŸ‘† case uses the same project 🦐

Project case Git address: LinDaiDai/ bPMn-vue-custom Please give a Star🌟, thanks 😊

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 – HTTP Request

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

The most detailed BPMN.js textbook -contextPad

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