Skip to content

Flowchart builder, using the Toolkit's Vue2 integration.

Notifications You must be signed in to change notification settings

vadkasevas/vue2-flowchart-builder

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flowchart Builder (Vue 2)

This is a port of the Flowchart Builder application that demonstrates the Toolkit's Vue 2 ES6 module based integration, in a Vue CLI 3 application.

Flowchart Builder Demonstration

This page gives you an in-depth look at how the application is put together.

package.json

This file was created for us by the CLI, to which we then added these entries:

{
    "dependencies":{
        ...
        "jsplumbtoolkit": "file:./jsplumbtoolkit.tgz",  
        "jsplumbtoolkit-vue2": "file:./jsplumbtoolkit-vue2.tgz",
        "jsplumbtoolkit-undo-redo": "file:./jsplumbtoolkit-undo-redo.tgz"
        ...
    }
}

You need to copy these files into the project root from either your licensed or evaluation bundle prior to running npm install.

TOP


Setup

As this application was generated by the CLI, the setup was done for us. We just had to add the appropriate entries to package.json.

TOP


Bootstrap

A CLI application is bootstrapped through src/main.js. Ours looks like this:

import Vue from 'vue'
import App from './App.vue'
import { JsPlumbToolkitVue2Plugin } from 'jsplumbtoolkit-vue2';

Vue.config.productionTip = false

require('@/assets/css/jsplumbtoolkit.css');
require('@/assets/css/jsplumbtoolkit-demo-support.css');
require('@/assets/css/jsplumbtoolkit-editable-connectors.css');
require('@/assets/css/app.css');

Vue.use(JsPlumbToolkitVue2Plugin);

new Vue({ render: h => h(App) }).$mount('#app')

If you were using the Vue integration with a version prior to 2.1.0 your setup will have been slightly different - you would not have the Vue.use(JsPlumbToolkitVue2Plugin) call. From 2.1.0 onwards the Vue2 integration is provided with the templates precompiled, meaning you do not need to include the runtime compiler in your app. If for some reason you don't want to make any changes to your pre-2.1.0 application, you can import jsplumbtoolkit-vue2-runtime.tgz instead of jsplumbtoolkit-vue2.tgz, which contains the original Vue2 integration code. The inclusion of this package is only a temporary measure, though - we will stop including it at some point in the not so distant future.

If you're using jsplumbtoolkit-vue2-runtime.tgz then your main.js would look like this:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

require('@/assets/css/jsplumbtoolkit.css');
require('@/assets/css/jsplumbtoolkit-demo-support.css');
require('@/assets/css/jsplumbtoolkit-editable-connectors.css');
require('@/assets/css/app.css');

new Vue({ render: h => h(App) }).$mount('#app')

TOP


Application

The component that acts as the entry point of the application is defined in App.vue, which looks like this:

<template>
  <div id="app">
    <div class="jtk-demo-main" id="jtk-demo-flowchart">
      <div style="display:flex">
        <Palette surface-id="surface" selector="[data-node-type]"/>
        <div id="canvas" class="jtk-demo-canvas">
          <Controls surface-id="surface" />
          <Flowchart surface-id="surface" />
        </div>
      </div>
      <div class="description">
        <p>
          This sample application is a copy of the Flowchart Builder application, using the Toolkit's
          Vue 2 integration components and Vue CLI 3.
        </p>
        <ul>
          <li>Drag new nodes from the palette on the left onto the workspace to add nodes</li>
          <li>Drag from the grey border of any node to any other node to establish a link, then provide a description for the link's label</li>
          <li>Click a link to edit its label.</li>
          <li>Click the 'Pencil' icon to enter 'select' mode, then select several nodes. Click the canvas to exit.</li>
          <li>Click the 'Home' icon to zoom out and see all the nodes.</li>
        </ul>
      </div>
    </div>

  </div>
</template>

<script>

    import Vue from "vue"

    import  { Dialogs, jsPlumbToolkit } from "jsplumbtoolkit"

    import Flowchart from './components/Flowchart.vue'
    import Palette from './components/Palette.vue'
    import Controls from './components/Controls.vue'

    export default {
        name: 'app',
        components: {
            Flowchart, Palette, Controls
        },
        mounted:function() {
            jsPlumbToolkit.ready(() => {
                Dialogs.initialize({
                    selector: ".dlg"
                });
            });
        }
    }
</script>

Points to note:

  • The template uses 3 components that are also declared in this app - Flowchart, Palette and Controls. A discussion of each of these is below.
  • We initialise the Toolkit's Dialogs inside a jsPlumbToolkit.ready(..) function in the mounted callback of this component. You may not wish to use the Toolkit's Dialogs; they are something we created for our demonstrations. It isn't always necessary to wrap their initialisation in a ready(..) call, either, but in this demonstration we load them from an external file called templates.html and need to be sure they have loaded before we try to initialise them.

Flowchart Component

This is where most of the functionality is coordinated. We'll break it up into sections and go through each one.

Template

<template>
    <jsplumb-toolkit 
        ref="toolkitComponent" 
        url="flowchart-1.json" 
        v-bind:render-params="renderParams" 
        v-bind:view="view" 
        id="toolkit" 
        surface-id="surface" 
        v-bind:toolkit-params="toolkitParams">
        
    </jsplumb-toolkit>
</template>

We use the jsplumb-toolkit component, providing several pieces of information:

  • ref="toolkitComponent" we want to be able to retrieve this component after mounting, as we need a reference to the underlying Toolkit instance for some of our business logic
  • url="flowchart-1.json" we provide the url for an initial dataset to load. This is of course optional.
  • v-bind:render-params="renderParams" These are the parameters passed to the Surface component that renders the dataset. As we will see below, we declare these in the data section of the Flowchart component.
  • v-bind:view="view" This the view passed to the Surface component that renders the dataset. As we will see below, we declare this in the data section of the Flowchart component.
  • id="toolkit" We give an id to the Toolkit instance we are creating, which is optional, and in fact in this app we do not use it.
  • surface-id="surface" We assign an ID to the surface for a couple of reasons: first, we need to nominate which surface to attach our miniview, controls and palette components to. Second, we need to access the surface for some of our app's functionality.
  • v-bind:toolkit-params="toolkitParams" These are the parameters passed to constructor of the Toolkit instance. As we will see below, we declare these in the data section of the Flowchart component.

Script Block

We'll break this up into parts too.

Imports
import Vue from 'vue'
import { jsPlumbToolkit, jsPlumb, Dialogs, DrawingTools, jsPlumbUtil } from 'jsplumbtoolkit'
import { jsPlumbToolkitVue2, Palette } from 'jsplumbtoolkit-vue2'

import StartNode from './StartNode.vue'
import ActionNode from './ActionNode.vue'
import QuestionNode from './QuestionNode.vue'
import OutputNode from './OutputNode.vue'

We import Vue and a few bits and pieces from the Toolkit, and from its Vue integration. We also import each of the components used to render our nodes.

Component Level Functionality

We have a few methods defined in the component's scope - things to perform operations on edges, plus the code we use as our Node Factory:

let toolkitComponent;
let toolkit;


function maybeRemoveEdge(params) {
    Dialogs.show({
        id: "dlgConfirm",
        data: {
            msg: "Delete Edge"
        },
        onOK: function () {
            params.toolkit.removeEdge(params.edge);
        }
    });
}

function editEdge(params) {
    Dialogs.show({
        id: "dlgText",
        data: {
            text: params.edge.data.label || ""
        },
        onOK: function (data) {
            toolkit.updateEdge(params.edge, {label:data.text});
        }
    });
}

function nodeFactory(type, data, callback)  {
    Dialogs.show({
        id: "dlgText",
        title: "Enter " + type + " name:",
        onOK: function (d) {
            data.text = d.text;
            // if the user entered a name...
            if (data.text) {
                // and it was at least 2 chars
                if (data.text.length >= 2) {
                    // set an id and continue.
                    data.id = jsPlumbUtil.uuid();
                    callback(data);
                }
                else
                // else advise the user.
                    alert(type + " names must be at least 2 characters!");
            }
            // else...do not proceed.
        }
    });
}
Component Definition

The key pieces to note here are:

  • we've declared toolkitParams, renderParams and view in our data object. You can find a discussion of these concepts in the documentation.
  • we map Vue components to node types in the view
  • we initialise the DrawingTools when the component is mounted.
export default {

    name: 'jsp-toolkit',
    props:["surfaceId"],
    data:() => {
        return {
            toolkitParams:{
                nodeFactory:nodeFactory,
                beforeStartConnect:function(node, edgeType) {
                    // limit edges from start node to 1. if any other type of node, return
                    return (node.data.type === "start" && node.getEdges().length > 0) ? false : { label:"..." };
                }
            },
            renderParams:{
              layout:{
                  type:"Spring"
              },
              jsPlumb:{
                  Connector:"StateMachine",
                  Endpoint:"Blank"
              },
              events:{
                  modeChanged:function (mode) {
                      let controls = document.querySelector(".controls");
                      jsPlumb.removeClass(controls.querySelectorAll("[mode]"), "selected-mode");
                      jsPlumb.addClass(controls.querySelectorAll("[mode='" + mode + "']"), "selected-mode");
                  },
                  edgeAdded:(params) => {
                      if (params.addedByMouse) {
                          editEdge(params);
                      }
                  }
              },
              lassoInvert:true,
              elementsDroppable:true,
              consumeRightClick: false,
              dragOptions: {
                  filter: ".jtk-draw-handle, .node-action, .node-action i"
              }
            },
            view:{
                nodes: {
                    "start": {
                        component:StartNode
                    },
                    "selectable": {
                        events: {
                            tap: (params) => params.toolkit.toggleSelection(params.node)
                        }
                    },
                    "question": {
                        parent: "selectable",
                        component:QuestionNode
                    },
                    "action": {
                        parent: "selectable",
                        component:ActionNode
                    },
                    "output":{
                        parent:"selectable",
                        component:OutputNode
                    }
                },
                // There are two edge types defined - 'yes' and 'no', sharing a common
                // parent.
                edges: {
                    "default": {
                        anchor:"AutoDefault",
                        endpoint:"Blank",
                        connector: ["Flowchart", { cornerRadius: 5 } ],
                        paintStyle: { strokeWidth: 2, stroke: "#f76258", outlineWidth: 3, outlineStroke: "transparent" },	//	paint style for this edge type.
                        hoverPaintStyle: { strokeWidth: 2, stroke: "rgb(67,67,67)" }, // hover paint style for this edge type.
                        events: {
                            "dblclick": maybeRemoveEdge
                        },
                        overlays: [
                            [ "Arrow", { location: 1, width: 10, length: 10 }],
                            [ "Arrow", { location: 0.3, width: 10, length: 10 }]
                        ]
                    },
                    "connection":{
                        parent:"default",
                        overlays:[
                            [
                                "Label", {
                                    label: "${label}",
                                    events:{
                                        click:editEdge
                                    }
                                }
                            ]
                        ]
                    }
                },

                ports: {
                    "start": {
                        edgeType: "default"
                    },
                    "source": {
                        maxConnections: -1,
                        edgeType: "connection"
                    },
                    "target": {
                        maxConnections: -1,
                        isTarget: true,
                        dropOptions: {
                            hoverClass: "connection-drop"
                        }
                    }
                }
            }
        };
    },

    mounted() {

        toolkitComponent = this.$refs.toolkitComponent;
        toolkit = toolkitComponent.toolkit;

        jsPlumbToolkitVue2.getSurface(this.surfaceId, (s) => {
            new DrawingTools({
                renderer: s
            });
        });
    }

}

Node Components

Each of the four node types is rendered with a specific Vue component. With the exception of the StartNode component, they each include the BaseEditableNode mixin, whose definition is:

<script>

    import { Dialogs } from 'jsplumbtoolkit'
    import { BaseNodeComponent } from 'jsplumbtoolkit-vue2'

    export default {
        mixins:[ BaseNodeComponent ],
        methods:{
            edit:function() {
                let node = this.getNode();
                Dialogs.show({
                    id: "dlgText",
                    data: node.data,
                    title: "Edit " + node.data.type + " name",
                    onOK: (data) => {
                        if (data.text && data.text.length > 2) {
                            // if name is at least 2 chars long, update the underlying data and
                            // update the UI.
                            this.updateNode(data);
                        }
                    }
                });
            },
            maybeDelete:function() {
                let node = this.getNode();
                Dialogs.show({
                    id: "dlgConfirm",
                    data: {
                        msg: "Delete '" + node.data.text + "'"
                    },
                    onOK:() => {
                        this.removeNode();
                    }
                });
            }
        }
    }

</script>

It offers 2 common methods - to handle editing of a node's label, and to handle a node's deletion.

Note in the node templates we write a v-pre attribute on jtk-source and jtk-target elements. This instructs Vue to ignore these; without v-pre Vue would try to render these as Vue components.

StartNode

<template>
    <div v-bind:style="{left:obj.left + 'px', top:obj.top + 'px', width:obj.w + 'px', height:obj.h + 'px'}" class="flowchart-object flowchart-start">
        <div style="position:relative">
            <svg :width="obj.w" :height="obj.h">
                <ellipse :cx="obj.w/2" :cy="obj.h/2" :rx="obj.w/2" :ry="obj.h/2" class="outer"/>
                <ellipse :cx="obj.w/2" :cy="obj.h/2" :rx="(obj.w/2) - 10" :ry="(obj.h/2) - 10" class="inner"/>
                <text text-anchor="middle" :x="obj.w / 2" :y="obj.h / 2" dominant-baseline="central">{{obj.text}}</text>
            </svg>
        </div>
        <jtk-source port-type="start" filter=".outer" filter-negate="true" v-pre/>
    </div>
</template>

<script>
    export default { }
</script>

ActionNode

<template>
    <div v-bind:style="{left:obj.left + 'px', top:obj.top + 'px', width:obj.w + 'px', height:obj.h + 'px'}" class="flowchart-object flowchart-action">
        <div style="position:relative">
            <div class="node-edit node-action">
                <i class="fa fa-pencil-square-o" v-on:click="edit()"/>
            </div>
            <div class="node-delete node-action">
                <i class="fa fa-times" v-on:click="maybeDelete()"/>
            </div>
            <svg :width="obj.w" :height="obj.h">
                <rect x="0" y="0" :width="obj.w" :height="obj.h" class="outer drag-start"/>
                <rect x="10" y="10" :width="obj.w-20" :height="obj.h-20" class="inner"/>
                <text text-anchor="middle" :x="obj.w/2" :y="obj.h/2" dominant-baseline="central">{{obj.text}}</text>
            </svg>
        </div>
        <jtk-target port-type="target" v-pre/>
        <jtk-source port-type="source" filter=".outer" v-pre/>
    </div>
</template>

<script>
    import BaseEditableNode from './BaseEditableNode.vue'
    export default {
        mixins:[BaseEditableNode]
    }
</script>

OutputNode

<template>
    <div v-bind:style="{left:obj.left + 'px', top:obj.top + 'px', width:obj.w + 'px', height:obj.h + 'px'}" class="flowchart-object flowchart-output">
        <div style="position:relative">
            <div class="node-edit node-action">
                <i class="fa fa-pencil-square-o" v-on:click="edit()"/>
            </div>
            <div class="node-delete node-action">
                <i class="fa fa-times" v-on:click="maybeDelete()"/>
            </div>
            <svg :width="obj.w" :height="obj.h">
                <rect x="0" y="0" :width="obj.w" :height="obj.h"/>
                <text text-anchor="middle" :x="obj.w/2" :y="obj.h/2" dominant-baseline="central">{{obj.text}}</text>
            </svg>
        </div>
        <jtk-target port-type="target" v-pre/>
    </div>
</template>

<script>
    import BaseEditableNode from './BaseEditableNode.vue'
    export default {
        mixins:[BaseEditableNode]
    }
</script>

QuestionNode

<template>
    <div v-bind:style="{left:obj.left + 'px', top:obj.top + 'px', width:obj.w + 'px', height:obj.h + 'px'}" class="flowchart-object flowchart-action">
        <div style="position:relative">
            <div class="node-edit node-action">
                <i class="fa fa-pencil-square-o" v-on:click="edit()"/>
            </div>
            <div class="node-delete node-action">
                <i class="fa fa-times" v-on:click="maybeDelete()"/>
            </div>
            <svg :width="obj.w" :height="obj.h">
                <path :d="'M ' +  (obj.w/2) + ' 0 L ' + obj.w + ' ' + (obj.h/2) + ' L ' + (obj.w/2) + ' ' + obj.h + ' L 0 ' + (obj.h/2) + ' Z'" class="outer"/>
                <path :d="'M ' + (obj.w/2) + ' 10 L ' + (obj.w-10) + ' ' + (obj.h/2) + ' L ' + (obj.w/2) + ' ' + (obj.h-10) + ' L 10 ' + (obj.h/2) + ' Z'" class="inner"/>
                <text text-anchor="middle" :x="obj.w/2" :y="obj.h/2" dominant-baseline="central">{{obj.text}}</text>
            </svg>
        </div>
        <jtk-target port-type="target" v-pre/>
        <jtk-source port-type="source" filter=".outer" v-pre/>
    </div>
</template>

<script>
    import BaseEditableNode from './BaseEditableNode.vue'
    export default {
        mixins:[BaseEditableNode]
    }
</script>

Dragging New Nodes

In this demonstration, new nodes can be dragged on to whitespace in the canvas, to create new, unconnected, nodes. They can also be dragged onto an existing edge, in which case the new node is injected in between the two nodes at either end of the edge on which the new node was dropped.

We declare a Palette component in the app's template:

<Palette surface-id="surface"
     selector="[data-node-type]"
     v-bind:data-generator="dataGenerator"
     allowDropOnEdges="true">
</Palette>

Palette is a component declared in this application, which uses the DragDrop mixin from the Toolkit's Vue integration:

<template>
    <div class="sidebar node-palette">
        <div class="sidebar-item" :data-node-type="entry.type" title="Drag to add new" v-for="entry in data" :key="entry.type">
            <i :class="entry.icon"></i>{{entry.label}}
        </div>
    </div>
</template>

<script>

    import { DragDrop } from 'jsplumbtoolkit-vue2-drop';

    export default {
        mixins:[ DragDrop ],
        data:function() {
            return {
                data:[
                    { icon:"icon-tablet", label:"Question", type:"question" },
                    { icon:"icon-eye-open", label:"Action", type:"action" },
                    { type:"output", icon:"icon-eye-open", label:"Output" }
                ]
            };
        },
        methods:{
            onCanvasDrop:function(surface, data, positionOnSurface) {
                data.left = positionOnSurface.left;
                data.top = positionOnSurface.top;
                surface.getToolkit().addFactoryNode(data.type, data);
            },
            // disabling linter so you can see all of the method arguments
            // eslint-disable-next-line
            onEdgeDrop:function(surface, data, edge, positionOnSurface, el, evt, pageLocation) {
                let toolkit = surface.getToolkit();
                toolkit.addFactoryNode(data.type, data,
                    function(newNode) {
                        let currentSource = edge.source; // the current source node
                        let currentTarget = edge.target; // the target node
                        toolkit.removeEdge(edge);
                        toolkit.addEdge({source:currentSource, target:newNode, data:{label:"...", type:"connection"}});
                        toolkit.addEdge({source:newNode, target:currentTarget, data:{label:"...", type:"connection"}});
                        surface.setPosition(newNode, positionOnSurface.left, positionOnSurface.top);
                    }
                );
            }
        }
    }

</script>

Dropping a new node onto whitespace

The onCanvasDrop method here handles dropping a new node onto the canvas. We copy in the left and top values from the positionOnSurface argument to the data object. We then call addFactoryNode on the underlying Toolkit instance, with the type of the new node and the data for the node. addFactoryNode is a method that will cause the current nodeFactory to be invoked - in this demonstration, we provide a nodeFactory that pops up a dialog, requesting the user enter a label for the new node.

Dropping a new node onto an existing edge

The onEdgeDrop method handles this case. There are a number of arguments passed to this callback method. positionOnSurface is an object with { left:.., top:... } values that are in the coordinate space of the surface, adjusted for its current pan and zoom.

In this callback we again set left and top on the data object, and we call addFactoryNode, but here we provide a callback function as the third argument. This method is called at the very end of the process of adding a node via the node factory. We store the source and target of the edge on which the new node was dropped, then we remove that edge. We then add an edge from the original source to the new node, and another edge from the new node to the original target. Finally, we instruct the surface to place the new node at the location on the canvas at which the user dropped the object.

TOP


Controls Component

The buttons in the top left of the screen are handled by the component defined in Controls.vue. Here's the full code for the component; a discussion follows below.

<template>
    <div class="controls" ref="container">
        <i class="fa fa-arrows selected-mode" mode="pan" title="Pan Mode" v-on:click="panMode()"></i>
        <i class="fa fa-pencil" mode="select" title="Select Mode" v-on:click="selectMode()"></i>
        <i class="fa fa-home" reset title="Zoom To Fit" v-on:click="zoomToFit()"></i>
        <i class="fa fa-undo" undo title="Undo last action" v-on:click="undo()"></i>
        <i class="fa fa-repeat" redo title="Redo last action" v-on:click="redo()"></i>
        <i class="fa fa-times" title="Clear" v-on:click="clear()"></i>
    </div>
</template>

<script>

    import { jsPlumbToolkitVue2 } from "jsplumbtoolkit-vue2";
    import { jsPlumbToolkitUndoRedo } from "jsplumbtoolkit-undo-redo";
    
    let undoManager;
    let container;
    let surfaceId;

    // a wrapper around getSurface, which expects a callback, as the surface may or may not have been
    // initialised when calls are made to it.
    function getSurface(cb) {
        jsPlumbToolkitVue2.getSurface(surfaceId, cb);
    }

    export default {
        props:["surfaceId"],
        methods:{
            panMode:function() {
                getSurface((s) => s.setMode("pan"));
            },
            selectMode:function() {
                getSurface((s) => s.setMode("select"));
            },
            zoomToFit:function() {
                getSurface((s) => s.zoomToFit());
            },
            undo:function() {
                undoManager.undo();
            },
            redo:function() {
                undoManager.redo();
            },
            clear: function() {
                getSurface((s) => {
                    const t = s.getToolkit();
                    if (t.getNodeCount() === 0 || confirm("Clear canvas?")) {
                        t.clear();
                    }
                });
            }
        },
        mounted:function() {

            surfaceId = this.surfaceId;
            container = this.$refs.container;
            getSurface((surface) => {

                undoManager = new jsPlumbToolkitUndoRedo({
                    surface:surface,
                    compound:true,
                    onChange:(mgr, undoSize, redoSize) => {
                        container.setAttribute("can-undo", undoSize > 0);
                        container.setAttribute("can-redo", redoSize > 0);
                    }
                });

                surface.bind("canvasClick", () => {
                    surface.getToolkit().clearSelection();
                });
            });
        }
    }

</script>


Referencing the component

The Controls component is created by the template in App.vue:

<div id="canvas" class="jtk-demo-canvas">
  <Controls surface-id="surface"></Controls>
  <Flowchart surface-id="surface"></Flowchart>
</div>

We pass the same value for surface-id as we pass to the Flowchart component. Each component uses the underlying Toolkit's Vue2 service to access the Surface with this id. Note we pass the value in "kebab case" but the actual property is in camel case.

Mounting the component

The component's mounted function does four things:

  • Sets the component wide surfaceId property (from the value specified in App.vue's template)
  • Sets the component's container property. This is the DOM element hosting the component.
  • Creates a new undo/redo manager referencing the Surface in use by the app
  • Binds an event listener to the canvasClick event to clear the current selection whenever a user clicks on whitespace in the canvas.

Behaviour

Each button is mapped to a method specified in the component's exports. Note, in these methods, the use of the getSurface(..) helper method to access the Surface. This method simply abstracts out the surfaceId from each of the individual handlers.

Pan Mode

Puts the Surface into "pan" mode (the lasso is disabled)

panMode:function() {
    getSurface((s) => s.setMode("pan"));
}

Select Mode

Puts the Surface into "select" mode (the lasso is enabled)

selectMode:function() {
    getSurface((s) => s.setMode("select"));
}

Zoom the contents to fit the viewport

This will adjust the zoom and pan so that the content is centered and zoomed to fit.

zoomToFit:function() {
    getSurface((s) => s.zoomToFit());
}

Undo the last action

We call the undo() method of the Undo/Redo Manager we created:

undo:function() {
    undoManager.undo();
}

Redo the last action

We call the redo() method of the Undo/Redo Manager we created:

redo:function() {
    undoManager.redo();
}

Clear the canvas

To clear the canvas we call the clear method of the underlying Toolkit. Note that we call clear() on the Toolkit even if there are no nodes in the dataset, which may seem odd, but we do this because then a few internal events are fired which will restore the canvas to the state the user expects.

clear: function() {
    getSurface((s) => {
        const t = s.getToolkit();
        if (t.getNodeCount() === 0 || confirm("Clear canvas?")) {
            t.clear();
        }
    });
}

TOP

About

Flowchart builder, using the Toolkit's Vue2 integration.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published