Skip to main content

Managing the data model

There is a full API available for you to manage your data model programmatically. Broadly, the operations on the data model can be broken up into three main categories:

There are also some additional operations available on groups - the addition/removal of child vertices.

Whenever an operation occurs on the model, the Toolkit advises all of its attached renders to update themselves accordingly.

note

The methods discussed on this page are relevant when using "vanilla" jsPlumb Toolkit as well as when using the Angular/Vue/React/Svelte integrations.

Adding model objects

Nodes

There are two ways to add a node, either directly via the addNode method, whose signature is:

addNode(data: ObjectData, eventInfo?: any): Node

for example:

toolkit.addNode({id:"1", foo:"a value", type:"someType"})

or, if you have a node factory setup on your instance, you can add a node by invoking the factory, via the addFactoryNode method, whose signature is:

addFactoryNode(type: string, data?: ObjectData, continueCallback?: Function): void

Consider that you have a node factory that does a fetch to some endpoint, providing the type for the new node, and the endpoint responds with the data for your new node:

import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance({
nodeFactory:function(type, data, callback, evt, native) {
fetch({type:"post", body:{type}}).then(v => callback(v))
}
};

You can invoke the factory in a few ways. Firstly you can invoke the factory without supplying any data of your own:

toolkit.addFactoryNode("someType");

It is possible to also provide some seed data for the new node, which will be passed in to the node factory as data:

toolkit.addFactoryNode("someType", { foo:"bar" });

And also you can provide a callback which will be run after the node factory has finished adding the new node:

toolkit.addFactoryNode("someType", { foo:"bar" }, (newNode) => {
// newNode is your new node.
});

Groups

As with nodes, you can add a group directly using addGroup, whose signature is:

addGroup(data: ObjectData, eventInfo?: any): Group

for example:

toolkit.addGroup({id:"1", foo:"a value", type:"someType"})

or you can setup a group factory and use the addFactoryGroup method, whose signature is:

addFactoryGroup(type: string, data?: ObjectData, continueCallback?: Function): void
import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance({
groupFactory:function(type, data, callback, evt, native) {
fetch({type:"post", body:{type}}).then(v => callback(v))
}
};

You can call addFactoryGroup just by supplying the desired type for your new group:

toolkit.addFactoryGroup("someType");

Or by supplying type and initial data:

toolkit.addFactoryGroup("someType", { foo:"bar" })

Or by supply type, data and a callback:

toolkit.addFactoryGroup("someType", { foo:"bar" }, (newGroup) => {
// newGroup is your new group
});

Ports

Ports reside on nodes or groups, and so to add a port you have to supply the vertex you wish to add the port to. To add a port directly to a node or group you use addPort:

addPort(vertex: string | Node | Group, data: ObjectData): Port

So, for instance:

const node = toolkit.addNode({id:"one", type:"someType"})
const port = toolkit.addPort(node, {id:"p1", type:"somePortType"})

The equivalent to the addFactoryNode and addFactoryGroup method for ports is:

addNewPort(obj: string | Node | Group, type: string, portData?: ObjectData)

addNewPort will invoke the Toolkit's port factory and then call addPort with the port factory's result.

As an example, this is the port factory from an early version of our Schema Builder starter app, in which table columns are represented as ports on table nodes:

import { newInstance } from "@jsplumbtoolkit/browser-ui"

const toolkit = newInstance({
portFactory:(node, type, data, callback) => {
let column = {
id:data.columnName,
name:data.columnName[0].toUpperCase() + data.columnName.slice(1),
datatype:"varchar",
type:"column"
};
// add to node's data. we have to do this manually. the Toolkit does not know our internal
// data structure.
node.data.columns.push(column);
// handoff the new column.
callback(column);
}
})

const node = toolkit.addNode({id:"one", type:"table", columns:[]})
// this call will invoke the portFactory declared above:
toolkit.addNewPort(node, "column", (column) => {
// `column` is your new port, and has been added to node one.
})

note

The addPort and addNewPort methods discussed here will add ports to model objects, but they will not necessarily update the underlying JSON dataset. It is important to be across the concepts discussed regarding the synchronization of port data

Edges

You can add an edge programmatically to the dataset via the addEdge method:


export interface AddEdgeOptions {
source:Vertex|string
target:Vertex|string
geometry?:any
data?:any
cost?:number
directed?:boolean
}

addEdge(params: AddEdgeOptions): Edge

Adding an edge is slightly different to adding vertices, in that the backing data for an edge should be provided in a data object inside the payload you pass to this method. With nodes, groups and ports the backing data is at the top level.

The source and target you pass to this method can either be objects of type Vertex (Node, Group or Port), or they can be the ID of some vertex, in the case of ports this being a fully qualified port id in dotted notation. Some examples:

Here we add an edge from some Node to a port which we specify by id. We also supply some data for the edge - in this case, a label. The resulting edge has an ID that the Toolkit assigns:

const edge = toolkit.addEdge({source:someNode, target:"someOtherNode.somePortId", data:{label:"My Edge Label"}})

Here we add an edge between two vertices specified by id. We also supply some data for the edge - in this case, the edge's ID, and also a label:

const edge = toolkit.addEdge({
source:"node1",
target:"node2",
data:{
label:"Label",
id:"edgeId"
}
})

Removing model objects

Nodes

Nodes can be removed with the removeNode method, whose signature is:

removeNode(node: string | Vertex): JsPlumbToolkit

for example:

toolkit.removeNode("someNodeId")

Groups

Groups can be removed with the removeGroup method:

removeGroup(group: string | Group, removeChildren?: boolean):void

Note the removeChildren option to this method. By default when you remove a group its child vertices are orphaned, or if the group is itself a child of some other groups its child vertices are added to the group's parent.

This will remove a group and leave its child vertices in the dataset:

toolkit.removeGroup("someNodeId")

This will remove a group and its child vertices (and if any of those child vertices is a group, its children will be removed etc):

toolkit.removeGroup("someNodeId", true)

Ports

Use the removePort method to remove a port from the dataset:

removePort(vertexOrId: string | Node | Group | Port, portId?: string): boolean

This method can be called in a few ways.

With full port id

You can provide the full port id in dotted notation, from which the Toolkit will resolve the node/group and the port:

toolkit.removePort("someNode.somePortId")

With a port object

You can provide a Port model object:

const port = toolkit.addPort(someNode, { id:"portId", foo:"foo"})

...

toolkit.removePort(port)

With a node/group and port id

Lastly, you can provide the node/group that is the port's parent, and the port id:

toolkit.removePort(someNode, "portId")
note

We again refer you to the documenation regarding the synchronization of port data. Removing ports using these methods will remove the ports from the model, but to ensure the data is removed from the dataset you will need to have configured the Toolkit to recognize where your port data is stored on your nodes/groups.

Edges

You can remove an edge via removeEdge, whose signature is:

removeEdge(edge: string | Edge):JsPlumbToolkit

Edges can be removed by supplying the edge ID (see the example above for how to set an edge id):

toolkit.removeEdge("edgeId")

or by supplying an Edge model object:


const edge = toolkit.addEdge({source:someNode, target:"someOtherNode.somePortId", data:{label:"My Edge Label"}})

...

toolkit.removeEdge(edge)

Updating model objects

Nodes

To update a node, use updateNode or updateVertex:

updateNode(node: string | Node | ObjectData, updates?: ObjectData): void

updateVertex(vertex: string | Node | Group | Port | ObjectData, updates?: ObjectData): void
toolkit.updateNode("someNodeId", { foo:"new value"})

Groups

To update a group, use updateGroup (or updateVertex as shown above in the removing nodes section):

toolkit.updateGroup("someNodeId", { foo:"new value"})

Ports

To update a port, use the updatePort method, whose signature is:

updatePort(port: Port | string, updates?: ObjectData): void

You can call this method with either a full port id:

toolkit.updatePort("someNode.somePortId", {field:"updatedValue"})

or with a Port model object:

const port = toolkit.addPort(someNode, { id:"portId", field:"originalValue"})
toolkit.updatePort(port, {field:"updatedValue"})

Edges

Edges can be updated with the updateEdge method, whose signature is:

updateEdge(obj: Edge | string, updates?: ObjectData): void

As with removeEdge, the edge in question can be identified by its id or by an Edge object. Here we update our edge from before via its id with a new label:

toolkit.updateEdge("edgeId", {label:"New Label"})

Notifying the Toolkit of an update

If your data has been updated external to the Toolkit, you can advise the Toolkit after the fact. To do this, just call updateNode, updatePort, updateEdge or updateGroup with a null data object, eg:

myToolkitInstance.updateNode("aNodeId")

Undo/redo and transactions

All of the methods discussed on this page are connected to the underlying undo/redo stack, and run inside a transaction.

Certain methods can cause a cascade of other operations on the data model:

  • removal of a vertex causes the removal of all edges to that vertex and to any ports on the vertex
  • removal of a group with removeChildren:true will cause the removal of any child vertices, and edges connected to them

Since each method runs inside a transaction, calling undo() on a Toolkit instance will result in the rollback of every operation executed on the data model in response to some specific method call. Calling redo() will result in the re-application of every operation executed by the original method call.


Events

The various add, update and remove methods discussed on this page will each cause a corresponding event to be fired:

MethodEventPayload
addNodeEVENT_NODE_ADDED{data:ObjectData, node:Node, parentGroup?:Group}
updateNodeEVENT_NODE_UPDATED{updates:ObjectData, vertex:Node
removeNodeEVENT_NODE_REMOVED{node: Node, nodeId: string, edges: Array<Edge>, parentGroup?:Group }
addGroupEVENT_GROUP_ADDED{data:ObjectData, node:Node, parentGroup?:Group}
updateGroupEVENT_GROUP_UPDATED{updates:ObjectData, vertex:Node
removeGroupEVENT_GROUP_REMOVED{node: Group, groupId: string, edges: Array<Edge>, parentGroup?:Group }
addPortEVENT_PORT_ADDED{data:ObjectData, vertex:Vertex, port:Port}
updatePortEVENT_PORT_UPDATED{updates:ObjectData, vertex:Node
removePortEVENT_NODE_REMOVED{vertex: Vertex, port:Port, edges: Array<Edge> }
addEdgeEVENT_EDGE_ADDED{edge:Edge}
updateEdgeEVENT_EDGE_UPDATED{updates:ObjectData, edge:Edge, originalData:ObjectData}
removeEdgeEVENT_EDGE_REMOVED{edge: Edge }