IO / Loading and Saving Data

Loading & Saving Data

Getting data into and out of the Toolkit is easy.

Node, Edge and Port Data

Before we start, a quick note about what happens to the data you load.

Nodes

When you load a Node such as this:

{ "id":"123", "name":"Foo", "aValue":567 }

This JSON object is available on the resulting Node instance as the data variable. This object is also what is passed to the templating code. So this snippet of code would get you the Node that was loaded and then access aValue on the Node's data:

var node = toolkitInstance.getNode("123");
console.log(node.data.aValue);
--> 567

Ports

Ports are also backed with data. Consider a table in a database: in the Toolkit's example, we model a table as a Node, and each of the table's columns are Ports. Each column - each Port - has its own specific data such as ID, datatype, etc.

The loaders for the default JSON schemes add Nodes and Edges to the Toolkit. An example Node from the Database Visualizer is this:

{
  "id":"book",
  "name":"Book",
  "type":"table",
  "columns":[
    { "id":"id", "datatype":"integer", "primaryKey":true },
    { "id":"isbn", "datatype":"varchar" },
    { "id":"title", "datatype":"varchar" }
  ]
}

The Database Visualizer's port extractor function returns three Ports - the entries in the columns array. So the backing data for this first Port is:

{ "id":"id", "datatype":"integer", "primaryKey":true }

Edges

Edges also support the concept of being backed with data, but for Edges the Toolkit does not take the top-level JSON object - instead, it looks for a data member, eg:

edges:[
  { source:1, target:2, data:{ id:"edgeFu", name:"An Edge", aValue:"I am an edge" } }
  ...
]

The data object is what is passed to the jsPlumb code that binds edge definitions from the model to actual jsPlumb Connections. Here is the equivalent code to the snippet given for Nodes above:

var edge = toolkitInstance.getEdge("edgeFu");
console.log(edge.data.aValue);
--> "I am an edge"

Loading

To load data into an instance of the Toolkit, use the load method. You can load data from a remote source - using ajax - or directly from an object in memory. Note that this is an incremental load: the existing data is not cleared when you call load. If you wish to replace the entire dataset, first call the clear() method.

Loading data via ajax

var t = jsPlumbToolkit.newInstance();
t.load({
  type:"json",
  url:"someUrl.json",
  onload:function() {                
    // called after the data has loaded.    
  }
});

Specifying HTTP Headers

By default, the Toolkit will set the HTTP header Accept to the value application/json. You can override this by providing your own HTTP headers in the load call:

var t = jsPlumbToolkit.newInstance();
t.load({
  type:"xml",
  url:"someUrl.xml",
  headers:{
    Accept:"text/xml",
    X-My-Header:"FOO"
  },
  onload:function() {                
    // called after the data has loaded.    
  }
});

Note: if you provide headers you must set an Accept header (if you want it to be set), because the Toolkit will not write a value for Accept into an HTTP headers object that does not have it.

Loading data from memory

var t = jsPlumbToolkit.newInstance();
t.load({
  type:"json",
  data:{
    nodes:[
      { id:"foo", value1:"foo", value2:34 },
      { id:"bar", value1:"bar", value2:24 }
    ],
    edges:[
      { source:"foo", target:"bar", data: { value1:89 } }
    ]
});

Note that load returns the Toolkit instance, so in the case of loading data from memory you can chain methods that operate on the Toolkit:

t.load({
  type:"json",
  data: { some data }
}).render({
  container:"someElement",
  layout:{
    type:"hierarchical"
  }
});

TOP


Appending Data

Data can be appended via the load method, since it does not cause the underlying Graph to be cleared.

TOP


Data Formats

The Toolkit ships with support for two JSON formats for loading and saving data. Should you need to, you can create custom load/save handlers for your own syntax.

Graph JSON Syntax

This JSON syntax - the default - consists of a JSON object with an array for nodes, edges and ports. All of these are optional, but of course if you declare an Edge then the Nodes for that Edge are expected to be present!

Example: Nodes and Edges
{
  "nodes": [
    { id:"1", name:"foo"  },
    { id:"2", name:"baz"  },
    { id:"3", name:"foo"  },
    { id:"4", name:"ding" },
    { id:"5", name:"dong" },
    { id:"6", name:"ping" },
    { id:"7", name:"pong" }
  ],
  "edges":[
    { source:"1", target:"2" },
    { source:"1", target:"3" },
    { source:"2", target:"4" },
    { source:"2", target:"5" },
    { source:"3", target:"6" },
    { source:"3", target:"7" }                
  ]
}
Example: Groups, Nodes and Edges
{
  "groups":[
    { "id":"group1", name:"Group One" },
    { "id":"group2", name:"Group Two" }
  ],
  "nodes": [
    { id:"1", name:"foo", group:"group1"  },
    { id:"2", name:"baz"  },
    { id:"3", name:"foo"  },
    { id:"4", name:"ding", group:"group2" },
    { id:"5", name:"dong", group:"group2" },
    { id:"6", name:"ping" },
    { id:"7", name:"pong" }
  ],
  "edges":[
    { source:"1", target:"2" },
    { source:"1", target:"3" },
    { source:"2", target:"4" },
    { source:"2", target:"5" },
    { source:"3", target:"6" },
    { source:"3", target:"7" }                
  ]
}
Ports

There are 3 ways to include Port information:

####### Original port JSON syntax

Here, we provide both nodeId and id for each port entry:

{
  "nodes": [
    { id:"1", name:"foo"  },
    { id:"2", name:"baz"  },
    { id:"3", name:"foo"  },
    { id:"4", name:"ding" },
    { id:"5", name:"dong" },
    { id:"6", name:"ping" },
    { id:"7", name:"pong" }
  ],
  "edges":[
    { source:"1.1", target:"2.1" },
    { source:"1", target:"3" },
    { source:"2.2", target:"4.1" },
    { source:"2", target:"5" },
    { source:"3", target:"6" },
    { source:"3", target:"7" }                
  ],
  "ports":[
      { "nodeId":"1", "id":"1" },
      { "nodeId":"2", "id":"1" },
      { "nodeId":"2", "id":"2" },
      { "nodeId":"4", "id":"1" }
  ]
}

####### New port JSON syntax

Here, we provide the full port id in dotted syntax instead (which is what the exporter puts out):

{
  "nodes": [
    { id:"1", name:"foo"  },
    { id:"2", name:"baz"  },
    { id:"3", name:"foo"  },
    { id:"4", name:"ding" },
    { id:"5", name:"dong" },
    { id:"6", name:"ping" },
    { id:"7", name:"pong" }
  ],
  "edges":[
    { source:"1.1", target:"2.1" },
    { source:"1", target:"3" },
    { source:"2.2", target:"4.1" },
    { source:"2", target:"5" },
    { source:"3", target:"6" },
    { source:"3", target:"7" }                
  ],
  "ports":[
      { "id":"1.1" },
      { "id":"2.1" },
      { "id":"2.2" },
      { "id":"4.1" }
  ]
}

####### Via a portExtractor or portDataProperty

See this page for details of these approaches.

Loading Graph JSON
toolkitInstance.load({
  type:"json",
  url:"http://mydata.com?xyz"
});

Since Graph JSON is the default syntax, you can in fact omit the type parameter in the above example:

toolkitInstance.load({
  url:"http://mydata.com?xyz"
});

TOP


Hierarchical JSON Syntax

This JSON syntax represents hierarchical data, in which every Node may have zero or more child Nodes. It is assumed that there is an Edge between a Node and each of its child Nodes. Note that the children member on each Node's data is optional.

{
  "id":"foo",
  "name":"FOO!",
  "children":[
    {
      "id":"bar",
      "name":"BAR!",
      "children":[
        {
          "id":"baz",
          "name":"BAZ!"
        }
      ]
    },
    {
      "id":"qux",
      "name":"QUX!"
    }
  ]
}
Loading Hierarchical JSON
toolkitInstance.load({
  type:"hierarchical-json",
  url:"http://myhierarchicaldata.com?xyz"
});

TOP


Loading Custom Formats

To register your own data loader, do the following:

jsPlumbToolkitIO.parsers["myFormat"] = function(data, toolkit, parameters) {
  // process data here.
};

Your loader function is responsible for populating the Toolkit instance with Nodes, Edges and Ports, using the addNode, addEdge and addPort functions. These functions are discussed in the API documentation.

Note the parameters argument: you can pass parameters to the load function:

toolkitInstance.load({
  type:"myFormat",
  url:"http://myFormat.com",
  parameters:{
    doThis:true,
    doThat:false
  }
});

TOP


Saving

You can either tell the Toolkit to save data on demand, using the save function, or configure it to save automatically, whenever it detects that the data model has changed.

The Toolkit uses an HTTP POST to save data. You cannot configure it to use anything other than a POST. If you absolutely do not want to use POST, then you can use the method described at the bottom of this page to export the data, then save it yourself.

Save on demand

var t = jsPlumbToolkit.newInstance();
...
t.save({
  type:"json",
  url:"http://foo.com",
  parameters:{ "foo":1, "bar":2 },
  success:function() {  },
  error:function() {  },
  headers:{ "Optional":"Http Headers"}
})

Here, as with the load function, the type of the dataset is assumed to be the Toolkit's default JSON syntax (see below for an example of how to write a custom exporter).

The allowed parameters are:

  • type By default this is set to "json". Specifies the data type in which to format the data. This must match the name of an exporter registered with the given instance of the Toolkit.
  • url Required. URL to POST data to.
  • parameters Optional parameters to pass to the exporter. If you write a custom exporter you may wish to use this.
  • success Optional callback to execute once the data has saved successfully.
  • error Optional callback to execute if there was an error saving the data.
  • headers Optional headers to set on the ajax request. By default, the Toolkit will send a Content-Type:"application/json" header. If you provide your own headers this header will continue to be sent, unless of course you override it.

Save Automatically

You can configure the Toolkit to save automatically, either with its own ajax call, or by invoking a function you supply.

The bare minimum setup requires the autoSave flag to be set, and then either a saveUrl:

var t = jsPlumbToolkit.newInstance({
  autoSave:true,
  saveUrl:"http://foo.com"
});

...or an autoSaveHandler function for the Toolkit to invoke:

var t = jsPlumbToolkit.newInstance({
  autoSave:true,
  autoSaveHandler:(toolkitInstance) => {
      
  }
});

The Toolkit will save the dataset whenever one of these methods is called:

  • addNode
  • removeNode
  • addNodes
  • addEdge
  • removeEdge
  • addPort
  • addNewPort
  • removePort
  • updateNode
  • updateEdge
  • updatePort
  • addToGroup
  • removeFromGroup

Note the dataset will not be saved automatically when you call the clear method.

If you find that having this switched on is causing too much network traffic due to rapid changes in the dataset, you can instruct the Toolkit to "debounce" these requests - limit the rate at which save requests will be made, via the autoSaveDebounceTimeout constructor parameter:

var t = jsPlumbToolkit.newInstance({
  autoSave:true,
  saveUrl:"http://foo.com",
  autoSaveDebounceTimeout:1000
});

This is a value in milliseconds.

Autosave Callbacks

You can supply callbacks for auto save success and error, as well as functions to run before and after an auto save operation. These functions are not invoked if you supply an autoSaveHandler, since the Toolkit in that case does not perform the save operation itself.

var t = jsPlumbToolkit.newInstance({
  autoSave:true,
  saveUrl:"http://foo.com",
  onAutoSaveError:function(msg) { ... },
  onAutoSaveSuccess:function(response) { ... },
  onBeforeAutoSave:function() { ... },
  onAfterAutoSave:function() { ... }
});

Autosave HTTP Headers

To set headers as discussed below when auto-saving, provide a saveHeaders object in the Toolkit's constructor.

These are only used when you supply a saveUrl. If you supply an autoSaveHandler these are, of course, ignored.

var toolkit = jsPlumbToolkit.newInstance({
  autoSave:true,
  saveUrl:"http://sava-data.com",
  saveHeaders:{
    "X-My-Header":"My Header Value"
  }
});

Saving Custom Formats

To save/export in your own format, do the following:

jsPlumbToolkitIO.exporters["myFormat"] = function(toolkit, parameters) {
  // process data here.
}

Your exporter function is responsible for retrieving the data from the Toolkit instance. There are a number of different ways this can be done - see the API documentation.

Content Type

The Toolkit will, by default, set the Content-Type header on a save POST to:

Content-Type:"application/json"

Specifying HTTP Headers

You can set arbitrary headers (including to override Content-Type) via the headers parameter on a save call:

toolkit.save({
  url:"http://save-data.com",
  headers:{
    "X-My-Header":"headerValue"
  }
});

TOP


Exporting

If you want to just export the data, you can do the following:

var t = jsPlumbToolkit.newInstance();
...
var data = t.exportData();

This will assume you want the data in the Toolkit's default JSON syntax. If you have a custom exporter you wish to use, you can specify that using the type parameter. You can also pass in parameters for the export:

var t = jsPlumbToolkit.newInstance();
...
var data = t.exportData({
  type:"myFormat",
  parameters:{
    importantNumber:34,
    somePrefix:"foo-"
  }
});

TOP