Skip to main content

· One min read
Simon Porritt

This is a minor release which contains a fix for an issue with the surface component using a selection as its data source.

The ability to render some selection of your dataset, as opposed to the entire dataset, is a powerful concept that can be used to create some very advanced apps, such as our Collapsible Hierarchy demonstration:

collapsible hierarchy language browser - jsPlumb Toolkit - JavaScript diagramming library that fuels exceptional UIs

and also the Neighbourhood Views demonstration:

neighbourhood views chemical element browser - jsPlumb Toolkit - JavaScript diagramming library that fuels exceptional UIs


Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· 2 min read
Simon Porritt

The main change in 6.18.0 is that we've introduced a brand new home for JsPlumb's apidocs - https://apidocs.jsplumbtoolkit.com. This site is far easier to navigate than our previous apidocs pages, with a cleaner layout and a search bar, and we've taken steps to setup our URL scheme such that going forward it will be easy to access the apidocs for some specific version.

apidocs for JsPlumb 6.18.0 - jsPlumb Toolkit, leading alternative to GoJS and JointJS

Using Typedoc for our apidocs has also enabled us to embed snippets of our apidocs into the main documentation, safe in the knowledge that these snippets will always be current:

embedded apidocs for JsPlumb 6.18.0 - jsPlumb Toolkit - JavaScript diagramming library that fuels exceptional UIs

Updates

In 6.18.0 we've also:

  • Fixed an issue with the type of a new port not being set correctly on the Port object via the addNewPort method on a JsPlumb Toolkit instance. The type was set on the backing data but no on the Port itself.
  • Fixed an issue whereby dragging multiple nodes would cause the miniview to fail to update properly, when in activeTracking mode
  • Fixed issue with Firefox wheel direction being opposite to other browsers
  • Made some updates to the endpoints and anchor docs

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· 8 min read
Simon Porritt

Angular's new(ish) Signals system is a great addition to their library, and one which we're keeping an eye on with a view to updating JsPlumb's Angular integration to help you make the best use of it.

Signals were introduced in Angular 16 and have been improved upon in Angular 17. We know that upgrading Angular versions is not something people take lightly, so for the time being we are focusing on what Angular 16 offers, and we'll be looking at ways of releasing support in JsPlumb.

Right now, though - if you're already using Angular 16 or 17 - we thought you might like to see how you can take advantage of signals with the current version of JsPlumb.

In the demonstration below we have 3 nodes that each have a name and a list of values. We render these with an Angular component called NodeComponent, whose template uses a computed property based on a signal to write out each node's current list of values - try clicking the Add a value button on one of these nodes:

In this article we're going to take a look at the steps involved in supporting this. If you just want to cut to the chase and download some code, it's available as a standalone Angular app on Github here

Node component

Let's start with the node component's template:

<div class="jtk-signals-node" style="display: flex;flex-direction: column;">
<div class="jtk-signals-delete" (click)="this.removeNode()"></div>
<div>{{obj['name']}}</div>
<div>{{valueList()}}</div>
<button (click)="addValue()" style="margin-top:0.5rem">Add a value</button>
</div>

The key thing to note here is {{valueList()}} - valueList is a computed property that is based on a signal.

Configuring a signal

The first thing we do is to declare a signal inside our node component:

import { signal, computed, OnInit, Component } from "@angular/core"
import { BaseNodeComponent } from "@jsplumbtoolkit/browser-ui-angular"

export class NodeComponent extends BaseNodeComponent implements OnInit {

//
// Declare a signal at the component level, and give it type (which is optional - you could use ObjectData, JsPlumb's default,
// or you could of course also use `any`...but we didn't suggest that!)
//
readonly objSignal = signal<MyDataObject>(undefined!);

}
info

MyDataObject is an interface created for this demonstration that defines the data object backing each node. In this case we have:

interface MyDataObject {
name:string
values:Array<number>
}

Configuring a computed property

Now that we have a signal we can create a property whose value will be computed from it. As shown above, our computed property is called valueList, and its a comma delimited dump of the node's values:

//
// This is a computed value which will be updated whenever `objSignal` gets updated. Here we concat the list of values
// in this node to a comma delimited string.
//
readonly valueList = computed( () =>
this.objSignal().values.join(",")
);

Initialize the signal

We've created our signal with no start value, so we're going to give it out start value inside ngOnInit:

ngOnInit() {
this.objSignal.set(this.obj as MyDataObject);
}

This setup would be sufficient to do an initial node render with the values array from our computed property. But now we need to ensure the signal stays current.

Updating the signal

The key here is our node component is going to listen to an update event on the underlying Toolkit, and when it receives an event for the node it is managing, it will update the signal. The setup for this comes in 3 parts:

Define an update listener

//
// Listens for an update event on the Toolkit. When the updated vertex's ID matches this component's vertex's id, we
// set a new value on `objSignal`.
//
private _signalBinding(n:{vertex:Node}) {
if (n.vertex.id === this.obj['id']) {
console.log(`Updating signal binder for vertex ${this.obj['id']}`)
this.objSignal.set(this.obj as MyDataObject)
}
}

We define this private _signalBinding method on our class, which takes an update payload as argument. If the node being updated is the same as the node this component is mapped to, then the objSignal is set with a new value. Angular will propagate this change to any computed properties, such as our valueList property.

Bind the update listener to the class

This step may seem counter-intuitive but bear with us. We're going to declare another private class level member and assign our update listener to it:

//
// Declare a class-level handler for JsPlumb's node update event. This is necessary in order to maintain the `this`
// reference correctly, and to be able to unbind the handler when this component is destroyed.
//
private _signalBinder!:(params:{vertex:Node}) => any

Register the update listener with the Toolkit

Now we can update our ngOnInit to assign a value to __signalBinder and bind that function to our Toolkit:

//
// In the init method we do three things:
//
// 1. Set the initial value on our signal
// 2. Store a reference to our update handler to a function bound to this class.
// 3. Register our bound handler on the Toolkit to listen for updates to this vertex
//
ngOnInit() {
this.objSignal.set(this.obj as MyDataObject);
this._signalBinder = this._signalBinding.bind(this)
this.toolkit.bind(EVENT_NODE_UPDATED, this._signalBinder)
}

There are two main reasons for this two-step approach to binding the update listener. Firstly, without it, the code will "lose" the this reference and be unable to access other class members, so that's kind of a show-stopper. But secondly, binding in this way lets us setup a nice way to cleanup if this component gets destroyed.

Cleaning up

If this component gets destroyed for some reason - which can happen via Angular maybe if a route changes or its parent gets unloaded, but can also happen when the related node is removed from the Toolkit - we're going to want to unbind our event listener from the Toolkit:

//
// When the component is destroyed, unbind the update listener.
//
override ngOnDestroy() {
console.log(`Destroy - unbinding signal binder for vertex ${this.obj['id']}`)
this.toolkit.unbind(EVENT_NODE_UPDATED, this._signalBinder)
super.ngOnDestroy()
}

Updating the value list

As mentioned above, when the user presses Add a value we invoke a method on the node component:

//
// When the user clicks Add a value, add a new value to the vertex's `values` array and update the object in the Toolkit.
// Our update listener will pick this up and update the signal, and Angular will handle the rest.
//
addValue() {
const newValues = this.obj['values'].slice()
newValues.push(Math.floor(Math.random() * 150))
this.updateNode({
values:newValues
})
}

The key thing to note here is that this method is unaware of the existence of the signal - it operates in the same way that methods that want to update the Toolkit always have done. It's the code above that provides the link between the Toolkit and the signal and its computed properties.

The code in full

This is the full code for the node component used in this demonstration:

import { signal, computed, OnInit, Component } from "@angular/core"
import { Node, EVENT_NODE_UPDATED } from "@jsplumbtoolkit/browser-ui"
import { BaseNodeComponent } from "@jsplumbtoolkit/browser-ui-angular"

interface MyDataObject {
name:string
values:Array<number>
}

@Component({
template:`<div class="jtk-signals-node" style="display: flex;flex-direction: column;">
<div class="jtk-signals-delete" (click)="this.removeNode()"></div>
<div>{{obj['name']}}</div>
<div>{{valueList()}}</div>
<button (click)="addValue()" style="margin-top:0.5rem">Add a value</button>
</div>`
})
export class NodeComponent extends BaseNodeComponent implements OnInit {

//
// Declare a signal at the component level, and give it type (which is optional - you could use ObjectData, JsPlumb's default,
// or you could of course also use `any`...but we didn't suggest that!)
//
readonly objSignal = signal<MyDataObject>(undefined!);

//
// This is a computed value which will be updated whenever `objSignal` gets updated. Here we concat the list of values
// in this node to a comma delimited string.
//
readonly valueList = computed( () =>
this.objSignal().values.join(",")
);

//
// Declare a class-level handler for JsPlumb's node update event. This is necessary in order to maintain the `this`
// reference correctly, and to be able to unbind the handler when this component is destroyed.
//
private _signalBinder!:(params:{vertex:Node}) => any

//
// Listens for an update event on the Toolkit. When the updated vertex's ID matches this component's vertex's id, we
// set a new value on `objSignal`.
//
private _signalBinding(n:{vertex:Node}) {
if (n.vertex.id === this.obj['id']) {
console.log(`Updating signal binder for vertex ${this.obj['id']}`)
this.objSignal.set(this.obj as MyDataObject)
}
}

//
// In the init method we do three things:
//
// 1. Set the initial value on our signal
// 2. Store a reference to our update handler to a function bound to this class.
// 3. Register our bound handler on the Toolkit to listen for updates to this vertex
//
ngOnInit() {
this.objSignal.set(this.obj as MyDataObject);
this._signalBinder = this._signalBinding.bind(this)
this.toolkit.bind(EVENT_NODE_UPDATED, this._signalBinder)
}

//
// When the component is destroyed, unbind the update listener.
//
override ngOnDestroy() {
console.log(`Destroy - unbinding signal binder for vertex ${this.obj['id']}`)
this.toolkit.unbind(EVENT_NODE_UPDATED, this._signalBinder)
super.ngOnDestroy()
}

//
// When the user clicks Add a value, add a new value to the vertex's `values` array and update the object in the Toolkit.
// Our update listener will pick this up and update the signal, and Angular will handle the rest.
//
addValue() {
const newValues = this.obj['values'].slice()
newValues.push(Math.floor(Math.random() * 150))
this.updateNode({
values:newValues
})
}
}

Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· 2 min read
Simon Porritt

Release 6.17.0 contains a few nice updates to the miniview component:

Zoom to canvas location

In the images below keep your eye on the mouse pointer over the miniview - when it is stationary, we are performing a wheel zoom. From 6.17.0 onwards the surface is zoomed in/out at the same point as the miniview:

miniview adjusts surface transform origin when zooming - jsPlumb Toolkit, leading alternative to GoJS and JointJS

Click to center nodes/groups

You can now click a node/group in a miniview and JsPlumb will scroll the related node/group in the surface to the center of the viewport - in the gif below, our user is clicking on individual elements in the miniview with the mouse (it also works with touch devices of course) :

click node/group in miniview to center it - jsPlumb Toolkit, leading alternative to GoJS and JointJS

This behaviour can be controlled via the clickToCenter flag in your miniview options. See below for a link to the miniview docs.

Tracking dragged elements

A further nice update in 6.17.0 is that the miniview now actively updates the view as nodes/groups are dragged in the related surface - watch the miniview in the image below:

miniview updates dragged elements as they are dragged - jsPlumb Toolkit, industry standard diagramming and rich visual UI Javascript library

This behaviour can be controlled via the activeTracking flag in your miniview options. See below for a link to the miniview docs.


Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· 2 min read
Simon Porritt

Orthogonal connector paths

We've made a major improvement to orthogonal connector path editing in version 6.16.0. In a nutshell, it's now far harder for a user to inadvertently get the UI into the situation that a connector doubles back onto itself underneath a vertex, which makes for a more intuitive experience. This is how the editor behaves in 6.16.0:

orthogonal connector vertex avoid - jsPlumb Toolkit - JavaScript diagramming library that fuels exceptional UIs

and this is how the editor behaves in versions prior to 6.16.0:

orthogonal connector vertex overlap - jsPlumb Toolkit - JavaScript diagramming library that fuels exceptional UIs

We think the behaviour in 6.16.0 is much nicer.

Vue 2 / Vue 3 integration

  • It's no longer necessary to declare the BaseNodeComponent or BaseGroupComponent mixin when using the Vue2 or Vue3 integration packages. These mixins are automatically added by JsPlumb if not specified in your component.

Miscellaneous updates

As we inch ever closer to our 7.x release we are continuing the work of reducing duplication and cruft from the codebase.

  • Several events that were previously fired by the UI layer but not consumed by the Surface have been removed, as have their associated constants.
  • The UI rendering layer's "lists" package has been removed. This package was part of the Community edition and was not exposed via the Toolkit API.

Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· One min read
Simon Porritt

New Functionality

  • When a new node is dropped onto some existing node via the drop manager, the onVertexAdded callback on SurfaceDropManager now passes back information about the vertex on which the new node was dropped.

  • Support for the onVertexAdded callback was added to ShapeLibraryPalette

  • Added new panWithMetaKey option to Surface render params. With this flag you can instruct a Surface to only pan when the user is holding down the "meta" key (command key on Macs, ctrl key on Windows).


Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· One min read
Simon Porritt

New Functionality

  • Added support for optional cornerRadius to Segmented connector

cornerRadius property on Segmented connector - jsPlumb Toolkit, flowcharts, chatbots, bar charts, decision trees, mindmaps, org charts and more

Updates

  • Added a fix for a very specific orthogonal geometry loading issue, involving nodes in a straight line that are close together.
  • Updated setPan method to fire a pan event.

Further reading


Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· One min read
Simon Porritt

Recently a licensee who is still using version 2.x of JsPlumb wanted to upgrade their app to use Angular 16, but did not have the bandwidth to undertake an upgrade to the latest 6.x version of JsPlumb. So we pulled down 2.4.16 (the last version in the 2.x line), dusted it off to support Angular 16+, and released it as 2.50.0.

Version 2.50.0 is available to all licensees who currently have access to downloads (including new licensees). We don't recommend using 2.50.0 in preference to 6.x, but if you find yourself in a similar situation you might like to consider it.

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· One min read
Simon Porritt

Updates

  • Fixed issue with path editor incorrectly trying to edit an edge that had been aborted by a beforeStart interceptor.

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.

· One min read
Simon Porritt

Updates

  • Fixed an issue with the surface's destroy method when a background image was in use.
  • Added autoSaveType option to Surface, allowing you to specify which exporter to use when auto save is switched on
  • Added optional type to AutoSave constructor, allowing you to specify which exporter to use when saving.

Breaking changes

  • The exportData method no longer writes a ports section into the output. You can use legacy-json as the type when exporting data if you still need this, but in 7.x that will be removed.

Start a free trial

Not a user of jsPlumb but thinking of checking it out? There's a whole lot more to discover and it's a great time to get started!


Get in touch!

If you'd like to discuss any of the ideas/concepts in this article we'd love to hear from you - drop us a line at hello@jsplumbtoolkit.com.