Drag Groups
A DragGroup
models a group of vertices that should be dragged together. You can have any number of drag groups in your canvas. When you assign a vertex to a drag group you indicate to JsPlumb whether that vertex should be an active
member - meaning that when it is dragged all of the other vertices in the drag group are also dragged - or that it should be a passive
member - meaning that when it is dragged no other members should be dragged, but that it should be dragged whenever an active
member is dragged.
Assigning drag groups
To assign vertices to drag groups you need to include the DragGroupsPlugin
:
import { newInstance,
DragGroupsPlugin } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"plugins": [
{
"type": DragGroupsPlugin.type,
"options": {
"assignDragGroup": (v) => {
return { id:'dragGroup', active:v.type === 'main' }
}
}
}
]
});
The key piece to note is the assignDragGroup
function that you provide. In the above example our assignDragGroup
function instructs JsPlumb to add every vertex to a group called "dragGroup"
, but that the vertex should only be an active
member if it has a type
of "main"
.
Consider this dataset:
{
nodes:[
{id:"1", left:50, top:50, type:"main" },
{id:"2", left:250, top:160 },
{id:"3", left:350, top:100 }
]
}
We have one "main"
vertex and two other vertices. In the canvas below, try dragging the large green box around. You'll see the two red boxes drag along with it. Now try dragging one of the red boxes - nothing else moves. This is because all of the nodes are inside a drag group, but the large green node is marked active
and the red nodes are marked passive
, due to the assignDragGroup
function shown above:
The key is the assignDragGroup
function that we provide. In the implementation above we do two things:
- all vertices are assigned to a drag group called
"dragGroup"
- The vertex whose
type
is"main"
is markedactive:true
; the others are markedactive:false
Multiple drag groups
Our example above just used a single drag group, but we can have as many of these as we want. For instance, here's a canvas in which all the red elements are dragged in a single group, and all the green elements are dragged in a different group:
This was an even simpler setup - we just use each node's type
to specify its drag group:
import { newInstance,
DragGroupsPlugin } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"plugins": [
{
"type": DragGroupsPlugin.type,
"options": {
"assignDragGroup": (v) => {
return v.type
}
}
}
]
});
Every element in this example is marked active
because that's the default if you do not specify it. All we had to do in this example is return the name of a drag group and JsPlumb adds the vertex as an active participant to that group.
Our dataset in this example is:
{
nodes:[
{id:"1", left:50, top:50, type:"green" },
{id:"2", left:150, top:160, type:"red" },
{id:"3", left:200, top:30, type:"red" },
{id:"4", left:250, top:100, type:"green" }
]
}
Example - annotating objects
A good example of how this functionality is useful is the concept of annotating objects in a diagram - explanatory notes for some given vertex that you want to place near the vertex, but whose positioning you want to adjust in each case to suit the diagram. When you drag a vertex that has annotations you want the annotations to move with the vertex, but you want to be able to position the annotations without moving the vertex itself.
Consider this dataset:
{
nodes:[
{ id:"1", type:"main", left:50, top:50 },
{ id:"2", type:"main", left:300, top:50 },
{ id:"3", type:"annotation", text:"I belong to node 1", ref:"1", left:70, top:-40 },
{ id:"4", type:"annotation", text:"I belong to node 1", ref:"1", left:-90, top:120 },
{ id:"5", type:"annotation", text:"I belong to node 2", ref:"2", left:380, top:160 }
]
}
We've got two nodes of type main
, and three nodes of type annotation
, each of which have a ref
member, which points to a main
node. We want to be able to drag our main
nodes around and have the annotation
nodes follow, but we also want to be able to position the annotation
nodes around the main
nodes where we please. This is easily achieved:
import { newInstance,
DragGroupsPlugin } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"plugins": [
{
"type": DragGroupsPlugin.type,
"options": {
"assignDragGroup": (v) => {
return v.type === 'main' ? v.id : {id:v.data.ref, active:false}
}
}
}
]
});
- For nodes of type
main
, we just return the node's id:v.id
- For nodes of type
annotation
, we return theref
as the drag group id, and mark the vertex passive:{id:v.data.ref, active:false}
.
Which gives us this arrangement:
And there you have it! Annotated objects using just a few lines of configuration.
Housekeeping
One thing to keep in mind is that the annotations and the edges that connect them to their reference nodes will not automatically be removed by JsPlumb if the reference node is removed from the dataset. Don't worry, though - we've got you. We'll use another of JsPlumb's capabilities you won't find in other libraries in this space - Transactions - to cleanup the annotations, but in an undo/redo friendly way.
Try clicking one of the red X buttons below. We'll remove the node the button belongs to, and we'll also remove any annotations that are attached to it (code follows below) :
To remove a node and its annotations in an undo-friendly way, we find everything we want to delete and then perform all the removals inside a transaction. An example function, into which you'd pass the Toolkit and the ID of the node to cleanup, is:
function removeNode(toolkit, nodeId) {
const annotations = toolkit.getNodes().filter(n => n.data.ref === nodeId)
toolkit.transaction(() => {
annotations.forEach(a => toolkit.removeNode(a))
toolkit.removeNode(nodeId)
})
}
How you wire up this function will depend on what library integration you are using.
In vanilla JS/Typescript the best approach is to use the modelEvents
render option:
import { newInstance, EVENT_TAP } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
toolkit.render(someElement, {
...,
modelEvents:[
{
selector:"[data-jtk-delete='true']",
event:EVENT_TAP,
callback:(event, element, info) => {
const annotations = toolkit.getNodes().filter(n => n.data.ref === info.obj.id)
toolkit.transaction(() => {
annotations.forEach(a => toolkit.removeNode(a))
toolkit.removeNode(info.obj.id)
})
}
}
]
})