Skip to main content

Svelte Integration

From version 5.6.0, the Toolkit offers integration with Svelte 3. We invite all feedback and suggestions from Svelte users for ways to take this initial integration and make it more useful.

Imports

The Svelte integration ships in the package @jsplumbtoolkit/browser-ui-svelte.

TOP


Setup

The Toolkit's Svelte integration offers a wrapper around the Surface component. Let's take a quick look at how you'd use one. We'll declare an App Svelte component that creates an instance of the Toolkit and renders it to a Surface:

<script>

import { newInstance, SurfaceComponent } from '@jsplumbtoolkit/browser-ui-svelte'
import { AbsoluteLayout } from '@jsplumbtoolkit/browser-ui'

import MyNodeComponent from './MyNodeComponent.svelte'

const toolkit = newInstance()

const viewParams = {
nodes:{
default:{
component:MyNodeComponent
}
}
}

const renderParams = {
layout:{
type:AbsoluteLayout.type
}
}

</script>

<div>
<SurfaceComponent viewParams={viewParams}
renderParams={renderParams}
toolkit={toolkit}/>
</div>


We create an instance of the Toolkit in the App component, and setup some parameters for our view and for configuring the surface. In our viewParams we map an imported component MyNodeComponent to the type "default", meaning that component will be used to render all the nodes in this app. One of the strengths of the Toolkit is of course the ability to declaratively map different components to different node types, but for the purposes of getting going here we just have the one.

MyNodeComponent could be any valid Svelte component. We'll use a simple component that illustrates how to render information from a node's backing data:

<script>
export let data;
export let toolkit;
export let surface;
export let vertex;
</script>

<div class="my-component">
<h3>{data.id}</h3>
</div>

Here, MyNodeComponent exposes four props that the Toolkit will attempt to set:

  • data - The node's backing data
  • toolkit - The underlying toolkit instance
  • surface - The Surface that is rendering this component
  • vertex - The model object representing the node being rendered

You don't have to expose these props on your components, but the Toolkit will attempt to set them, and you'll almost certainly want to access, at the very least, data. Having access to the Toolkit instance and vertex is also very useful - imagine, for instance, you wanted to add a "delete" button to this component. We could rewrite it to be:

<script>
export let data;
export let toolkit;
export let surface;
export let vertex;

function remove() {
toolkit.removeNode(vertex)
}
</script>

<div class="my-component">
<h3>{data.id}</h3>
<button on:click|preventDefault={remove}>X</button>
</div>

Remember, you need to export a prop in order for Svelte to set a value on it.


Components

We've seen the SurfaceComponent in action above, and in other Toolkit integrations such as React/Angular/Vue, there are components offered to let you add a miniview, or to support drag and drop of new nodes. The Svelte integration currently does not offer component wrappers for these, and this is because, while writing the Svelte integration it just didn't feel necessary: because Svelte is a little more "bare-bones" than other frameworks/libraries, many of the features you'll want to add are easily achieved using the same approach that you'd use with the "vanilla" Toolkit.

We'll show a few examples here. Note, though, that as newcomers to the world of Svelte we'd be happy to stand corrected about any incorrect assumptions we've made, and to add features to the Svelte integration to make it more useful to users of Svelte.

Adding a miniview

You can add a miniview to your app by configuring one in an onMount callback. Let's take the app component from above and add a #miniview element, then tell the Surface to add a miniview plugin using that element:

<script>

import { newInstance, SurfaceComponent } from '@jsplumbtoolkit/browser-ui-svelte'
import { AbsoluteLayout, MiniviewPlugin } from '@jsplumbtoolkit/browser-ui'

import { onMount } from "svelte"

import MyNodeComponent from './MyNodeComponent.svelte'

let surfaceComponent

const toolkit = newInstance()

const viewParams = {
nodes:{
default:{
component:MyNodeComponent
}
}
}

const renderParams = {
layout:{
type:AbsoluteLayout.type
}
}

//
// instantiate the miniview inside an onMount callback, to get access to the underlying `Surface`.
//
onMount(async() => {
let surface = this.surfaceComponent.getSurface()
surface.addPlugin({
type:MiniviewPlugin.type,
container:document.getElementById("miniview")
})
}

</script>

<div>
<SurfaceComponent viewParams={viewParams}
renderParams={renderParams}
toolkit={toolkit}
bind:this={surfaceComponent} />
<div id="miniview"></div>
</div>

For more information about Miniviews, see this page.

Dragging and dropping new nodes

A common requirement for applications built on the Toolkit is the ability to drag and drop new nodes onto the canvas. Let's add that to our app alongside the miniview we just added. To do this involves three steps:

  • first you need to write out the HTML representing nodes that are draggable, the structure for which is entirely up to you. We'll keep it simple with a ul here.
  • secondly, you need to ensure you've used bind to get a reference to your SurfaceComponent (as in the miniview example above)
  • lastly, you need to instantiate a SurfaceDropManager
<script>

import { newInstance, SurfaceComponent } from '@jsplumbtoolkit/browser-ui-svelte'
import { AbsoluteLayout, MiniviewPlugin } from '@jsplumbtoolkit/browser-ui'

import { onMount } from "svelte"

import MyNodeComponent from './MyNodeComponent.svelte'

let surfaceComponent // a reference to the SurfaceComponent this component creates

const toolkit = newInstance()

const viewParams = {
nodes:{
default:{
component:MyNodeComponent
}
}
}

const renderParams = {
layout:{
type:AbsoluteLayout.type
}
}

//
// instantiate the drop manager and miniview inside an onMount callback, to get access to the underlying `Surface`.
//
onMount(async() => {
let surface = this.surfaceComponent.getSurface()

surface.addPlugin({
type:MiniviewPlugin.type,
container:document.getElementById("miniview")
})

new SurfaceDropManager({
surface,
source:document.getElementById("node-palette"),
selector:"div",
dataGenerator:(el) => {
return {
type:el.getAttribute("data-type")
}
}
})
})

</script>

<div>

<SurfaceComponent viewParams={viewParams}
renderParams={renderParams}
toolkit={toolkit}
bind:this={surfaceComponent}/>

<div id="miniview"></div>
<div id="node-palette">
<div data-type="foo">FOO</div>
<div data-type="bar">BAR</div>
</div>

</div>

Note in the above code snippet:

  • we used bind:this={SurfaceComponent} on our app component to store a reference to the surface component, from which we will get the underlying Surface
  • we imported onMount from "svelte" and instantiated the drop manager in an onMount callback (because we want to access the SurfaceComponent that has been rendered)
  • we identify the container for draggable nodes via the source argument to SurfaceDropManager.
  • we use "div" to identify a CSS selector that is used to find all candidate draggables inside the draggable nodes container. You can use any valid CSS3 selector here.
  • the dataGenerator function is given the element that is being dragged, at the start of a drag, and is expected to return an object to the Toolkit containing, at a minimum, the object's type.

To read more about the drop manager, see this page

TOP


Base components and common functionality

Many apps using the Toolkit use different components to render different node types. For instance, in the the Svelte flowchart builder demonstration, three nodes types - question, action and output are all rendered with different components, but each needs the ability to support a "delete" button.

The developers of Svelte have not shown a great deal of enthusiasm for the concept of "base" components:

https://github.com/sveltejs/svelte/issues/4168

so to support common functionality for a set of components, the SurfaceComponent supports an injector - a function that, given some vertex, can return a map of props to append to the component used to render that vertex. In the demonstration linked above, for example, we add a remove and edit method to each node like this:

1. Declare some function to act as the injector in your app's script

<script>

...

/**
* Optional props injector for vertices. In this app we supply a manager to each vertex that offers remove and edit methods.
* @param vertex
* @return {{}}
*/
function injectManager(vertex) {
return {
manager:{
remove: (vertex) => {
toolkit.removeNode(vertex)
},
edit:(vertex) => {
dialogs.editName(vertex.data, (d) => {
if (d.text && d.text.length > 2) {
// if name is at least 2 chars long, update the underlying data and
// update the UI.
toolkit.updateNode(vertex, d);
}
})

}
}
}
}

...

2. Set this on the SurfaceComponent

<SurfaceComponent viewParams={viewParams}
renderParams={renderParams}
toolkit={toolkit}
bind:this={surfaceComponent}
injector={injectManager}
/>

Now any component used to render any vertex in this demonstration will have a manager exposed on it, with remove and edit methods. Note that we could have inspected vertex in the injectManager method and returned different values for different vertices if we wanted to.

Here's the component used to render action nodes, for example:

<script>

export let data;
export let toolkit;
export let surface;
export let vertex;

export let manager

</script>

<div style="width:{data.w}px;height:{data.h}px"
class="flowchart-object flowchart-action"
data-jtk-target="true"
data-jtk-port-type="target">

<svg width={data.w} height={data.h}>
<rect x="10" y="10" width={data.w-20} height={data.h-20} class="inner" rx="5" ry="5"/>
</svg>
<span>{data.text}</span>
<div class="node-edit node-action" on:click={() => manager.edit(vertex)}></div>
<div class="node-delete node-action" on:click={() => manager.remove(vertex)}></div>
<div class="drag-start connect" data-jtk-source="true" data-jtk-port-type="source"></div>
</div>
note

This component declares export let manager: this is a requirement if you want the return value from the injector to be set on the component.