Skip to content

Latest commit

 

History

History
316 lines (265 loc) · 10.6 KB

customToolbar_design_en_us.md

File metadata and controls

316 lines (265 loc) · 10.6 KB

English | 简体中文

Overview

This article is about how to use vue and mxGraph to implement a custom toolbox.

Mainly complete the following functions:

  • Customize the contents of the toolbox.
  • Drag a tool item from the toolbox to the drawing area and generate the corresponding cell.
  • Double click the generated cell to view the cell data.

This example can be seen on [github] (https://github.com/lanniu/vue-mxgraph-example).

Basic layout

image

The basic layout we envision is shown in the figure above. The entire interface is divided into two parts, the left is the toolbox, and the right is our drawing area.

Since style is not our focus this time, we will show the tool items in a list. Of course you can also use some third-party component libraries such as element-ui, ant design vue, vuetify to achieve some cool display effects.

Defining the model

First we need to define the data structure of the toolbox:

[
    {
        icon: String || Object, // Path or object of the tool item icon
        title: String, // The name of the tool item
        width: Number, // Tool item default width
        height: Number, // Tool item default height
        style: {
            ... // Cell style generated by the tool item
        }
    }
]

It can be seen that the toolbox is an array, which stores multiple tool item objects, and each tool item contains multiple attributes.

It should be noted that the style attribute. The information contained in this attribute is used to describe the style of the cell generated by this tool item after dragging. For the value range, please refer to [official api] (https: // jgraph .github.io / mxgraph / docs / js-api / files / util / mxConstants-js.html # mxConstants).

After understanding the data structure, we began to define the specific data:

const outputIcon = './icon/output.png'
const inputIcon = './icon/input.png'

export const toolbarItems = [
  {
    icon: outputIcon,
    title: '输出',
    width: 50,
    height: 50,
    style: {
      fillColor: 'transparent',
      strokeColor: '#000000',
      strokeWidth: '1',
      shape: MxConstants.SHAPE_LABEL,
      align: MxConstants.ALIGN_CENTER,
      verticalAlign: MxConstants.ALIGN_BOTTOM,
      imageAlign: MxConstants.ALIGN_CENTER,
      imageVerticalAlign: MxConstants.ALIGN_TOP,
      image: outputIcon
    }
  },
  {
    icon: inputIcon,
    title: '输入',
    width: 50,
    height: 50,
    style: {
      fillColor: 'transparent', // fillColor
      strokeColor: '#000000', // strokeColor
      strokeWidth: '1', // strokeWidth
      shape: MxConstants.SHAPE_LABEL, // shape
      align: MxConstants.ALIGN_CENTER, // align
      verticalAlign: MxConstants.ALIGN_BOTTOM, // vertical align
      imageAlign: MxConstants.ALIGN_CENTER, // image align
      imageVerticalAlign: MxConstants.ALIGN_TOP, // image vertical align
      image: inputIcon // image
    }
  }
]

(All pictures in this example are placed in the /public directory)

For the convenience of maintenance, we put the above content into the toolbar.js file. Then import it in the vue component and save it as a calculated property called toolbarItems.

import {toolbarItems} from './customToolbar/toolbar'

export default {
  computed: {
    toolbarItems: () => toolbarItems
  }
}

Writing templates

With the data defined, we started writing template code.

First we need a large container for the toolbox and drawing area.

<div class="customToolbarContainer">
</div>

Then we add the toolbox code:

<div class="customToolbarContainer">
    <div class="toolbarContainer">
        <ul>
            <li v-for="item in toolbarItems" :key="item['title']" ref="toolItem">
                <img :src="item['icon']" :alt="item['title']">
                <span>{{item['title']}}</span>
            </li>
        </ul>
    </div>
</div>

Then we add the drawing area code:

<div class="customToolbarContainer">
    <div class="toolbarContainer">
        <ul>
            <li v-for="item in toolbarItems" :key="item['title']" ref="toolItem">
                <img :src="item['icon']" :alt="item['title']">
                <span>{{item['title']}}</span>
            </li>
        </ul>
    </div>
    <div class="graphContainer" ref="container"></div>
</div>

Finally we need a bit of css (I used scss):

.customToolbarContainer {
  width: 100%;
  height: 100%;
  display: flex;
  position: relative;

  .toolbarContainer {
    flex: 1;
    font-size: 20px;
    background: #efefef;
    text-align: center;
    padding-top: 20px;

    ul {
      padding: 0;
      margin: 0;

      li {
        list-style: none;
        margin-bottom: 10px;
        cursor: pointer;

        img {
          width: 15px;
          height: 15px;
        }

        span {
          margin-left: 15px;
        }
      }
    }
  }

  .graphContainer {
    position: relative;
    flex: 7;
  }
}

Now when we launch the webpage, we should see the following effect: image

Writing business code

Now that everything is ready, we start writing specific code.

First we need to declare a graph object in vue's data to hold the mxGraph instance.

data(){
    return {
        graph: null
    }
}

First define a method named createGraph to instantiate the mxGraph object:

createGraph() {
  this.graph = new MxGraph(this.$refs.container)
}

Then define a method named initGraph to initiate some attribute and event:

initGraph() {
  if (this.R.isNil(this.graph)) {
    return
  }
  this.graph.setConnectable(true) // Allow connection
  this.graph.setCellsEditable(false) // Unchangeable
  this.graph.convertValueToString = (cell) => { // Generate display labels based on cell
    return this.R.prop('title', cell)
  }
  this.graph.addListener(MxEvent.DOUBLE_CLICK, (graph, evt) => { // Listen for double-click events
    const cell = this.R.pathOr([], ['properties', 'cell'], evt)

    console.info(cell) // Output double-clicked cell in the console
  })
}

In the above code, several graph instance methods are used, and the following are introduced one by one:

  • setConnectable The setting is to allow new connections.
  • setCellsEditable Set whether to allow double-clicking to modify the label of the cell directly.
  • convertValueToString This is a function that receives a cell and returns the label that the cell uses for display. Here is a rewrite of the default method.
  • addListener Add a double-click listener for the graph.

Then define a method named addCell to add cells to the drawing area:

addCell(toolItem, x, y) {
  const {width, height} = toolItem
  const styleObj = toolItem['style']
  const style = Object.keys(styleObj).map((attr) => `${attr}=${styleObj[attr]}`).join(';')
  const parent = this.graph.getDefaultParent()

  this.graph.getModel().beginUpdate()
  try {
    let vertex = this.graph.insertVertex(parent, null, null, x, y, width, height, style)

    vertex.title = toolItem['title']
  } finally {
    this.graph.getModel().endUpdate()
  }
}

addCell receives three parameters: tool item object, X coordinate of Cell, and y coordinate of Cell.

The following steps were performed in the method:

  • Get the default width and height information of the cell.
  • Splice style into the form "xxx = xxx; xxx = xxx;".
  • Call the model's beginUpdate method to start a transaction.
  • Invoke the insertVertex method to add a cell.
  • Call the model's endUpdate method to end a transaction.

The next step is to implement the core code of the toolbox. Let's create a new method called ** initToolbar **:

initToolbar() {
  const domArray = this.$refs.toolItem

  if (!(domArray instanceof Array) || domArray.length <= 0) {
    return
  }
  domArray.forEach((dom, domIndex) => {
    const toolItem = this.toolbarItems[domIndex]
    const {width, height} = toolItem

    const dropHandler = (graph, evt, cell, x, y) => {
      this.addCell(toolItem, x, y)
    }
    const createDragPreview = () => {
      const elt = document.createElement('div')

      elt.style.border = '2px dotted black'
      elt.style.width = `${width}px`
      elt.style.height = `${height}px`
      return elt
    }

    MxUtils.makeDraggable(dom, this.graph, dropHandler, createDragPreview(), 0, 0, false, true)
  })
}

The purpose of the method is to allow the tool item to be dragged and trigger a custom callback event when letting go. The following steps were performed in the method:

  1. Get all tool items dom.
  2. Iterate through all doms.
    1. Gets the tool item object corresponding to the dom.
    2. Gets the default width and height of the tool item.
    3. Define a callback function for drop and execute the addCell method in the callback function.
    4. Defines a method for generating a preview. This method returns a div as a preview graphic.
    5. Call the makeDraggable method.

It should be noted that the makeDraggable method, makeDraggable receives multiple parameters, we now need to pay attention to the first four:

  1. dom The dom element that needs to be dragged。
  2. graph The bound graph object.
  3. dropHandler Callback function when drop.
  4. preview Preview graphics while dragging.

Finally, let's add the following code to the mounted:

mounted() {
  this.createGraph()
  this.initGraph()
  this.initToolbar()
  this.$refs.container.style.background = 'url("./mxgraph/images/grid.gif")'
}

When we open the webpage, we can see the following effects:

When you double-click the cell, you can see the output information in the console:

We can make some extensions to this Demo in the actual project. For example, in addCell function, you can instantiate different objects for different tool items and save them in the value property of the cell. When you double-click the cell, you can pop up the dialog box and modify the value of the cell's value attribute.

end.