Inspectors were updated to ensure they are cleaned up when the port they are inspecting is removed from the dataset, or the node/group which the port they are inspecting belongs to has been removed from the dataset.
Surface Drop manager was updated to not assign active/hover classes to nodes and groups in the canvas when allowDropOnNode or allowDropOnGroup is set to false.
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:
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.
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:
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!
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
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" exportclassNodeComponentextendsBaseNodeComponentimplementsOnInit{ // // 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:
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(",") );
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.
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:
// // 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.objasMyDataObject) } }
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.
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
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.objasMyDataObject); 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.
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() }
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.
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" interfaceMyDataObject{ 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>` }) exportclassNodeComponentextendsBaseNodeComponentimplementsOnInit{ // // 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.objasMyDataObject) } } // // 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.objasMyDataObject); 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 }) } }
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:
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)
:
This behaviour can be controlled via the clickToCenter flag in your miniview options. See below for a link to the miniview docs.
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:
This behaviour can be controlled via the activeTracking flag in your miniview options. See below for a link to the miniview docs.
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:
and this is how the editor behaves in versions prior to 6.16.0:
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.
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).
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!