Vue 3 Integration
JsPlumb has solid integration with Vue 3, shipping in the package @jsplumbtoolkit/browser-ui-vue3
.
Vue3 versions of our Flowchart Builder, Schema Builder and Chatbot starter apps are available in the JsPlumb demonstrations organisation on Github.
Imports
"dependencies":{
"@jsplumbtoolkit/browser-ui-vue3":"7.2.0"
}
Setup
You need to import JsPlumb in your Vue bootstrap code:
import { createApp } from 'vue'
import App from './App.vue'
import { JsPlumbToolkitVue3Plugin } from "@jsplumbtoolkit/browser-ui-vue3"
const app = createApp(App)
app.use(JsPlumbToolkitVue3Plugin)
Quick start
Here's a basic setup that uses the SurfaceComponent
to render a couple of nodes and an edge:
<template>
<div style="width:100%;height:100%">
<SurfaceComponent :data="this.getData()"/>
<ControlsComponent/>
<MiniviewComponent/>
</div>
</template>
<script>
import { defineComponent } from "vue"
export default defineComponent({
methods:{
getData:function() {
return {
data:{
nodes:[
{ id:"1", label:"1", left:50, top:50},
{ id:"2", label:"TWO", left:250, top:250}
],
edges:[
{ source:"1", target:"2" }
]
}
}
}
}
})
</script>
In the above example the only attribute we set on the Surface
was data
, meaning the component uses a default Absolute
layout and a default component for rendering nodes. The Surface
, though, is highly configurable, mainly through its view
and renderParams
inputs. Here's the same example from before but with a few more things configured, and an Vue 3 component to render nodes with:
NodeComponent.vue
<template>
<div style="width:60px;60px;background-color:white;border:1px solid black;text-align:center">
<h3>{{obj.label}}</h3></div>
</template>
<script>
import { BaseNodeComponent } from '@jsplumbtoolkit/browser-ui-vue3'
export default {
mixins:[BaseNodeComponent]
}
</script>
<template>
<div style="width:100%;height:100%">
<SurfaceComponent :data="this.getData()"
:renderOptions="this.renderParams()"
></SurfaceComponent>
<ControlsComponent/>
<MiniviewComponent/>
</div>
</template>
<script>
import { defineComponent } from "vue"
import NodeComponent from './NodeComponent.vue'
export default defineComponent({
methods:{
getData:function() {
return {
data:{
nodes:[
{ id:"1", label:"1", left:50, top:50},
{ id:"2", label:"TWO", left:250, top:250}
],
edges:[
{ source:"1", target:"2" }
]
}
}
},
renderParams:function() {
return {
layout:{
type:"ForceDirected"
},
grid:{
size:{
w:50, h:50
}
}
}
},
view:function() {
return {
nodes:{
default:{
component:NodeComponent
}
}
}
}
})
</script>
Rendering Nodes and Groups
At the core of your UI will be a SurfaceComponent
, which provides the canvas onto which nodes, groups and edges are drawn. In the Quickstart section above we show a couple of examples of the SurfaceComponent
in action.
Each node or group in your UI is rendered as an individual component. In the first example above, we rely on JsPlumb's default Vue 3 component to render each node, but in the second we provide a component ourselves, although its quite simple. In a real world app your components will likely be more complex. As an example, consider the component we use to render nodes in the Flowchart Builder:
<script>
import { BaseNodeComponent } from '@jsplumbtoolkit/browser-ui-vue3'
export default {
mixins:[BaseNodeComponent],
props:["shapeLibrary", "anchorPositions"]
}
</script>
<template>
<div :style="{color:obj.textColor}" class="flowchart-object" data-jtk-target="true">
<span>{{obj.text}}</span>
<ShapeComponent :obj="obj" :shape-library="shapeLibrary"></ShapeComponent>
<div v-for="anchor in anchorPositions"
:class="'jtk-connect jtk-connect-' + anchor.id"
:data-jtk-anchor-x="anchor.x"
:data-jtk-anchor-y="anchor.y"
:data-jtk-orientation-x="anchor.ox"
:data-jtk-orientation-y="anchor.oy"
data-jtk-source="true"
data-jtk-port-type="source"></div>
<div class="node-delete node-action delete"></div>
</div>
</template>
Mapping types to components
The viewOptions
prop is used to map node/group types to Components, and to certain behaviours. For instance, this is the nodes
section of the viewOptions
for the flowchart builder starter app:
const view = {
nodes:{
default:{
component:NodeComponent,
// target connections to this node can exist at any of the given anchorPositions
anchorPositions,
// node can support any number of connections.
maxConnections: -1,
events: {
[EVENT_TAP]: (params) => {
edgeEditor.stopEditing()
// if zero nodes currently selected, or the shift key wasnt pressed, make this node the only one in the selection.
if (toolkit.getSelection()._nodes.length < 1 || params.e.shiftKey !== true) {
toolkit.setSelection(params.obj)
} else {
// if multiple nodes already selected, or shift was pressed, add this node to the current selection.
toolkit.addToSelection(params.obj)
}
}
},
inject:{
shapeLibrary:shapeLibrary,
anchorPositions:anchorPositions
}
}
}
}
The component
property in each mapping points to some Vue Component. If you omit component
from a mapping, JsPlumb will use a default node/group component. This is great for prototyping or for apps whose nodes do not have rich content.
Notice the inject
section in the node mapping above: it instructs JsPlumb to inject two props into the NodeComponent
when it instantiates one. In this case we pass in a shape library, which we use in this app to render shapes, and a set of anchor positions. You'll see these two props defined on the NodeComponent
, whose source is shown above.
BaseNodeComponent
This mixin exposes several helper methods and is responsible for notifying the Vue 3 core when a node has been initially rendered. It is automatically injected into any component used to render nodes.
Methods
- getNode() Gets the underlying Toolkit node that the component is rendering
- removeNode() Instructs the Toolkit to remove the node that the component is rendering. This will of course result in the destruction of the component.
- getToolkit() Gets the underlying Toolkit instance for the node the component is rendering.
- updateNode(data:any) Updates the underlying node that the component is rendering.
Nodes rendered with this mixin are fully reactive: calls to updateNode
will result in a repaint of the component
with no further effort involved on your part.
BaseGroupComponent
This mixin exposes several helper methods and is responsible for notifying the Vue 3 core when a group has been initially rendered. It is automatically injected into any component used to render groups.
Methods
- getGroup() Gets the underlying Toolkit group that the component is rendering
- removeGroup(removeChildNodes?:boolean) Instructs the Toolkit to remove the group that the component is rendering, and possibly all of the child nodes of the group too. This will of course result in the destruction of the component.
- getToolkit() Gets the underlying Toolkit instance for the group the component is rendering.
- updateGroup(data:any) Updates the underlying group that the component is rendering.
Groups rendered with this mixin are fully reactive: calls to updateGroup
will result in a repaint of the component with no further effort involved on your part.
Injecting values in nodes/groups
From version 5.13.7 onwards, you can provide a set of data values that you'd like to inject into a component via the inject
property of a node definition:
export default {
name: 'jsp-toolkit',
props:["surfaceId"],
data:() => {
renderParams:{ ... },
view:{
nodes:{
default:{
component:MyNode,
inject:{
injectedStaticValue:"My Static Value",
injectedDynamicValue:(node, toolkit) => `My Dynamic Value ${node.id}`
}
}
}
}
}
}
Each value you inject can be either a static value, or a function that takes as argument the current node (or group) and the underlying Toolkit instance, and which returns an appropriate value.
In the component itself you need to declare these in the props:
props:{
injectedStaticValue:String,
injectedDynamicValue:String
},
You can then access these values in your template:
<template>
<div class="injected-static">{{injectedStaticValue}}</div>
<div class="injected-dynamic">{{injectedDynamicValue}}</div>
</template>
Rendering Ports
The Vue 3 integration supports rendering ports. A good example for how to do this can be found in the code for the Schema Builder starter application.
Shape Libraries
A shape library is a set of named shapes that you will use to render SVG inside the vertices in your application. These are discussed in detail in the shape libraries and shape-sets pages; here we provide an overview of how to use these modules in a Vue app.
Creating a shape library
In Vue, a shape library is created the same way as in vanilla js. In the code below we create a shape library with flowchart and basic shapes, which we then inject into our SurfaceComponent
via its renderOptions
prop:
<script>
import { defineComponent } from "vue";
import { FLOWCHART_SHAPES, BASIC_SHAPES, ShapeLibraryImpl, DEFAULT } from "@jsplumbtoolkit/browser-ui"
const shapeLibrary = new ShapeLibraryImpl([FLOWCHART_SHAPES, BASIC_SHAPES])
export default defineComponent({
name:"flowchart",
methods:{
renderOptions:function() {
return {
shapes:{
library:shapeLibrary
}
}
},
viewOptions:function() {
return {
nodes:{
default:{
component:NodeComponent,
inject:{
shapeLibrary:shapeLibrary
}
}
}
}
}
}
})
</script>
<template>
<div>
<SurfaceComponent :renderOptions="this.renderOptions()"/>
</div>
</template>
Rendering an SVG shape
Shapes can be rendered with a ShapeComponent
, which is a component that expect to be given some shape library and the backing data for a vertex. In the code above we show viewOptions
with a mapping for nodes, that uses a NodeComponent
, into which JsPlumb injects a shapeLibrary
prop. Internally, that NodeComponent
will use a ShapeComponent
to render the SVG:
<script>
import { BaseNodeComponent } from '@jsplumbtoolkit/browser-ui-vue3'
export default {
mixins:[BaseNodeComponent],
props:["shapeLibrary", "anchorPositions"]
}
</script>
<template>
<div>
<span>{{obj.text}}</span>
<ShapeComponent :obj="obj" :shape-library="shapeLibrary"></ShapeComponent>
</div>
</template>
By default the shape component will render just the shape. If you want to add labels you can do so like this:
<script>
import { BaseNodeComponent } from '@jsplumbtoolkit/browser-ui-vue3'
export default {
mixins:[BaseNodeComponent],
props:["shapeLibrary", "anchorPositions"]
}
</script>
<template>
<div>
<span>{{obj.text}}</span>
<ShapeComponent :obj="obj" :shape-library="shapeLibrary" :showLabels="true" labelProperty="text"></ShapeComponent>
</div>
</template>
Adding your own shapes
To add your own shapes, you'll need to create a shape set. These are discussed on a separate page.
Dragging new nodes/groups
Palettes offer a means for your users to drag new nodes/groups onto your canvas. JsPlumb Vue offers a SurfaceDrop
mixin to support drag/drop of HTML elements, and a ShapeLibraryPaletteComponent
to support drag/drop of SVG shapes.
HTML elements
To configure your UI to drag and drop HTML elements onto your canvas, you'll use a SurfaceDrop
mixin. As an example of how to set one up, this is the palette from our Chatbot starter app:
<template>
<div class="jtk-schema-palette">
<div class="jtk-schema-palette-item" :data-type="entry.type"
title="Drag to add new" v-for="entry in data" :key="entry.type">
{{entry.label}}
</div>
</div>
</template>
<script>
import {defineComponent} from "vue"
import { SurfaceDrop } from '@jsplumbtoolkit/browser-ui-vue3'
import { TABLE, VIEW } from "../constants"
export default defineComponent({
mixins:[ SurfaceDrop ],
data:function() {
return {
data:[
{ label:"Table", type:TABLE },
{ label:"View", type:VIEW}
]
};
}
})
</script>
Now we reference this Palette
component inside the template of our app, providing a selector
, which identifies what child elements should be draggable, and a dataGenerator
, which is what JsPlumb uses to get an initial payload when the user starts dragging an element. Here we also show how to setup a dataGenerator
- the best way is to declare it in the data
section of your component as the template can easily reference it:
<script>
export default defineComponent({
name:"FlowchartBuilder",
methods:{ ... },
mounted() { }
})
</script>
<template>
...
<Palette selector="[data-type]"/>
...
</template>
Selector
Note that we used a selector of [data-type]
in this app - meaning, some element that has an data-type
attribute. You can use any valid CSS3 selector.
Valid markup
In our Palette
we use a div
for each draggable type, inside an element with flex-column
set on it. JsPlumb is very flexible, though - you can use any markup you like.
Surface ID
The SurfaceDrop
needs to know which surface it is going to be attached to, which is not shown in the code above. This is because from 6.70.0 onwards, when you omit the surfaceId
from a component, JsPlumb uses a default value. If we had a UI with more than one surface we could assign it an ID and then also tell our palette about it:
<template>
...
<Palette selector="[data-type]" :dataGenerator="dataGenerator"
surfaceId="myOtherSurface"/>
...
</template>
SVG shapes
In JsPlumb Vue, you can use a ShapePaletteComponent
. This is how you'd include it in a template:
<template>
<div id="app">
<SurfaceComponent :renderOptions="this.renderOptions()"
:viewOptions="this.viewOptions()"
url="copyright.json">
</SurfaceComponent>
<ShapePaletteComponent :data-generator="dataGenerator" initial-set="flowchart"></ShapePaletteComponent>
</div>
</template>
The :data-generator
prop is a function used to get an initial dataset when a user starts to drag a shape. You can see this defined in the methods
of the component code below.
Other important points to note:
-
The
shapeLibrary
is supplied in therenderOptions
which are passed to theSurfaceComponent
. TheShapePaletteComponent
retrieves the shape library from the surface, so don't forget this step. -
We
inject
theshapeLibrary
intoNodeComponent
in theviewOptions
.NodeComponent
declares ashapeLibrary
prop (shown below), which it then uses to draw shapes.
<script>
import NodeComponent from './Node.vue'
import { defineComponent } from "vue";
import {
FLOWCHART_SHAPES, BASIC_SHAPES,
ShapeLibraryImpl
} from "@jsplumbtoolkit/browser-ui"
const shapeLibrary = new ShapeLibraryImpl([FLOWCHART_SHAPES, BASIC_SHAPES])
export default defineComponent({
methods:{
viewOptions:function() {
return {
nodes:{
default:{
component:NodeComponent,
inject:{
shapeLibrary:shapeLibrary
}
}
}
}
},
renderOptions:function() {
return {
shapes:{
library:shapeLibrary
}
}
}
},
data:() => {
return {
shapeLibrary,
dataGenerator:(el) => {
return {
textColor:"#FF0000",
outline:"#000000",
fill:"#FFFFFF",
text:'Hello',
outlineWidth:2
}
}
}
}
})
</script>
Component List
This is a full list of the components that are available in JsPlumb Vue. For each we provide a sample usage, which does not necessarily use all of the props available for the given component.
SurfaceComponent
This component provides an instance of JsPlumb and a surface widget to render the contents. You do not instantiate either the JsPlumb Toolkit or the Surface yourself, the Vue 3 code handles that. If you subsequently want to access the Toolkit instance a good approach is to declare a ref
for the Toolkit component as shown below. After the example follows a brief discussion of how to get access to the Toolkit and to the Surface.
Example
<SurfaceComponent
ref="toolkitComponent"
:renderOptions="this.renderOptions()"
:toolkitOptions="this.toolkitOptions()"
:viewOptions="this.viewOptions()">
</SurfaceComponent>
Props
All props are optional.
id
Unique ID for the Toolkit instance. Can be used to retrieve a Toolkit instance from the jsPlumbToolkitVue3 module.surface-id
Unique ID for the Surface widget. Required if you wish to attach a Miniview or a Palette. Also useful if you wish to interact with a Surface, to perform operations such as zooming, centering on content, etc.renderOptions
Parameters to pass in to the constructor of the Surface widget. Note here we use thev-bind:
prefix to tell Vue that the object we are injecting is in the Vue instance's model.toolkitOptions
Parameters to pass in to the constructor of the Toolkit instance. Note again the use ofv-bind:
in our example above.viewOptions
View parameters. Views are discussed here.
In this example we supply renderOptions
, toolkitOptions
and viewOptions
to the return value of methods - the underlying code looks like this:
export default defineComponent({
name: 'some-component',
props:["surfaceId"],
methods:{
viewOptions:function() {
return {
nodes: {
"start": {
component:StartNode
},
...other node types
},
... rest of the view
}
},
toolkitOptions:function() {
return {
beforeStartConnect: (node) => {
// 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: "..."};
}
}
},
renderOptions:function() {
return {
layout:{
type:ForceDirectedLayout.type
},
... other render params.
]
}
}
})
Accessing the Toolkit and the Surface
You'll almost certainly want to access the underlying Toolkit instance, which is best done by declaring a ref
as shown above. This ref can be accessed when the component mounts, as in the snippet below. We also show here how to access a surface, which you do via the loadSurface
method from the Vue 3 integration package:
import { loadSurface } from '@jsplumbtoolkit/browser-ui-vue3';
let toolkit
export default defineComponent({
mounted() {
toolkit = this.$refs.toolkitComponent.toolkit
loadSurface((s) => {
// s is of type Surface.
})
}
})
loadSurface
takes a callback rather than passing the surface back directly. This is because there is no guarantee that a surface with the given ID exists - it may be not loaded yet. If you try to access a surface that is not yet loaded, your request is queued, and then subsequently when a surface with that ID is registered on the Vue integration all of the requests for that surface are served, in the order they originally arrived.
Specifying surface ID
In the example above, we did not tell JsPlumb what Surface we wanted, so JsPlumb just assumes we want a surface registered with the default id. But if we have more than one surface we can target them specifically by providing the surface ID as the first argument to loadSurface
:
import { loadSurface } from '@jsplumbtoolkit/browser-ui-vue3';
let toolkit
export default defineComponent({
mounted() {
toolkit = this.$refs.toolkitComponent.toolkit
loadSurface("myOtherSurface", (s) => {
// s is of type Surface.
})
}
})
MiniviewComponent
This is a component that provides a miniview that can be attached to some surface.
Example
<MiniviewComponent></MiniviewComponent>
Attributes
surface-id
ID for the surface widget to which to attach the Miniview. Optional; if you don't provide it, a default will be used.
ControlsComponent
Provides a component that offers a set of controls for some surface. If there is a lasso plugin installed on the given surface this component will show buttons for pan/select mode, otherwise it will not.
Example
<ControlsComponent></ControlsComponent>
Attributes
surface-id
ID for the surface widget to which to attach the controls. Optional; if you don't provide it, a default will be used.undoRedo
Defaults to true. Whether or not to show the undo/redo buttons.zoomToExtents
Defaults to true. Whether or not to show the zoom to extents button.orientation
"row" or "column". The default is "row".
ExportControlsComponent
Provides a component that offers a set of buttons a user can press to export the contents of some surface as SVG, PNG or JPG. Note that this only works when you are using a shape library in your UI. Canvases whose vertices are rendered as HTML elements will not generate output.
Example
<ExportControlsComponent></ExportControlsComponent>
Attributes
surface-id
ID for the surface widget to which to attach the controls. Optional; if you don't provide it, a default will be used.showLabel
Defaults to true. Whether or not to show a label on the controls.label
Optional label to use. Defaults to "Export : ".margins
Optional margins to set around the output. Defaults to 0. This is an object inPointXY
format.svgOptions
OptionalSvgExportUIOptions
controlling SVG output.imageOptions
OptionalImageExportUIOptions
controlling image output.allowSvgExport
Defaults to true.allowPngExport
Defaults to true.allowJpgExport
Defaults to true.
SurfaceDrop mixin
This mixin is a wrapper around the Drop Manager, which offers the ability to drop onto edges, nodes and the canvas itself.
Example
This is an example of a component that uses the SurfaceDrop mixin. We show, in the onCanvasDrop
method, an example of how this mixin can be used to replace the previous Palette mixin. Note, though, the onEdgeDrop
and onDrop
methods: these are, respectively, called when an element is dragged on an Edge or a Node/Group.
<template>
<div class="sidebar node-palette">
<div class="sidebar-item" :data-jtk-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 { SurfaceDrop } from '@jsplumbtoolkit/browser-ui-vue3';
export default {
mixins:[ SurfaceDrop ],
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" }
]
};
}
}
</script>
Note that this component itself doesn't declare any props
, and you are free to provide any template you wish to
render the component's data
. The underlying DragDrop
mixin's props
are:
- surfaceId:string Optional. The ID of the Surface to which to attach the Drop Manager. If not provided, the default will be used.
- selector:string Required. A CSS3 selector instructing the Toolkit how to identify which elements in the component represent draggable node types.
- dataGenerator:(el:HTMLElement) => T Optional. A function that can return a data object representing an element which is being dragged. This function is called as soon as an element starts to be dragged.
- allowDropOnGroup:boolean Optional, defaults to true. If true, then elements can be dropped onto nodes/groups, and in the event that occurs, the
onDrop
method will be called. - allowDropOnCanvas:boolean Optional, defaults to true. When an element is dropped on the canvas whitespace, it is added to the dataset and rendered.
- allowDropOnEdge:boolean Optional, defaults to true. If true, then elements can be dropped onto edges, and in the event that an element is dropped on an edge, a new node/group is added and inserted between the source and target of the original edge, and the original edge is discarded..
- typeGenerator:(data:T) => string Optional. A function that can return the correct type for some data object representing an element being dragged. By default the Toolkit will use the
type
member of the data object. - groupIdentifier:(d: T, el: HTMLElement) => boolean Optional. By default, the toolkit looks for a
jtk-is-group
attribute on an element being dragged. If found, with a value of"true"
, then the Toolkit assumes a group is being dragged. You can supply your own function to make this decision.
ShapePaletteComponent
The shape library plugin also makes available a jsplumb-shape-palette
component, which lets you drag and drop new shapes from a palette:
To use it, you need to have setup the shape library as shown above. Then, in your template:
<ShapePaletteComponent :shape-library="shapeLibrary"
:data-generator="dataGenerator"
initial-set="flowchart"/>
Props
- surfaceId:string Optional. The ID of the surface to which to attach the palette. If not set a default will be used.
- shapeLibrary:ShapeLibraryImpl Your shape library
- dragSize:Size Optional size to set on elements when they are being dragged. If this is omitted, the derived
iconSize
will be used. - iconSize:Size Optional, defaults to 150x100 pixels. This is the size to use for each icon. It will also be used for
dragSize
if that is not separately specified. - fill:string Optional, defaults to "#FFFFFF". Fill color to use for icons.
- outline:string Optional, defaults to "#000000". Color to use for icon outline.
- selectAfterDrop:boolean Optional, defaults to true. Instructs the palette to set a newly dropped vertex as the Toolkit's selection.
- canvasStrokeWidth:number Optional, defaults to 2. The stroke width to use when rendering dropped vertices on the canvas.
- paletteStrokeWidth:number Optional, defaults to 1. The stroke width to use when rendering icons in the palette.
- dataGenerator:DataGeneratorFunction Optional. Provide one of these if you wish to be able to set initial data for some icon that is about to be dragged on to the canvas. By default JsPlumb will look for attributes with a
data-jtk-
prefix and use these as the data. For instance an element withdata-jtk-type
anddata-jtk-label
attributes will have a data object withtype
andlabel
values populated from those attributes' values. - initialSet If your shape library has multiple sets but when the palette is first rendered you want to specify a single set to show, set this value.