Configuring connectivity
Connectivity is declared in your templates by specifying some part of the each vertex's DOM element that can act as a connection source and/or target, via a set of data-jtk-*** attributes.
Configuring source and target attributes
You can specify parts of your UI that should act as connection sources and/or targets using a set of data-jtk- attributes. A quick example:
<div data-jtk-source="true" data-jtk-target="true"></div>
This is about the most simple example possible: a div element that is declared to be both a source and target of connections established by dragging with the mouse. Note, though, that the surface widget will automatically exclude any elements with a data-jtk-source attribute from being able to instigate dragging, since once an element can act as a connection source it is not possible to also support element dragging: the user's intent would be ambiguous.
So, a more plausible real world scenario would be some element in which only part of it could act as a connection source:
<div data-jtk-target="true">
<h1>Some Vertex</h1>
<div data-jtk-source="true" class="dragFromHere"></div>
</div>
Note that we still put the data-jtk-target attribute on the root element since there is no ambiguity - when the element is behaving as a connection target it is not going to be the element that is currently being dragged.
Mapping to port types
In the example above there is no specific piece of information provided to JsPlumb to indicate that it should use anything other than the defaults for edges that are dragged. If you want finer-grained control over edge appearance you can use the data-jtk-port-type attribute on elements in your templates.
Consider an app in which our vertices are represented with data objects like this:
{
id:"1",
left:50,
top:50,
scope:"cadetblue"
}
In this example, we declare a few different port types in our view options, each of which contains declarations for the appearance of connectors attached to that type. For example, a connection from a cadetblue port will use a StateMachine connector, whereas darkseagreen and lightcoral use Orthogonal, and the lightcoral type also has an arrow overlay declared.
In the template we specify a port type to use for each element source via the data-jtk-port-type attribute: the value, in this example, is extracted from the scope in each node's data.
Try dragging an edge from each of these nodes and you'll see the port type mapping action:
import { newInstance,
StateMachineConnector,
OrthogonalConnector,
ArrowOverlay } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"view": {
"nodes": {
[DEFAULT]: {
"template": "<div data-jtk-port-type={{scope}}
data-jtk-source=\"true\"
data-jtk-target=\"true\">{{id}}</div>"
}
},
"ports": {
"cadetblue": {
"connector": StateMachineConnector.type
},
"darkseagreen": {
"connector": OrthogonalConnector.type
},
"lightcoral": {
"connector": OrthogonalConnector.type,
"connectorOverlays": [
{
"type": ArrowOverlay.type,
"options": {
"location": 1,
"width": 10,
"length": 6
}
}
]
}
}
}
});
Mapping ports to elements
Ports on a vertex have an ID, which must be unique across the ports on that vertex, and which can be addressed. We use ports in the data model of several of our applications - for instance, in the Schema Builder starter app, we have a table node type, which has a set of columns. Each column in a table is mapped to a specific port id. This allows us to define edges between columns on two tables, for instance we may have an edge from book.book_author_id to author.author_id; here book and author are table IDs, and book_author_id and author_id are port IDs.
JsPlumb offers two attributes to assist in mapping ports from your model into your UI.
Physical ports
You can map port IDs to DOM elements - what we refer to as physical ports - via the data-jtk-port attribute.
Here, we have two nodes that each contain a list of columns, and each column has an id, for instance:
{
id:"1",
left:50,
top:50,
columns:[
{id:"one", scope:"cadetblue"},
{id:"two", scope:"darkseagreen"},
{id:"three", scope:"cadetblue" }
]
}
We render each node this this template:
<div style="display:flex;flex-direction:column;>
<r-each in="columns">
<div data-jtk-scope="{{scope}}"
style="background-color:{{scope}}"
data-jtk-source="true"
data-jtk-target="true"
data-jtk-port="{{id}}">{{id}}</div>
</r-each>
</div>
Each of our column elements declares data-jtk-source and data-jtk-target to be true, meaning the element is both a source and target for edges dragged with the mouse. But the key piece is the data-jtk-port attribute: it indicates to JsPlumb that that element is the physical representation of the given port on that vertex. Any edges connected to the port with that ID on the vertex will be connected to that DOM element.
In the dataset for the canvas below we have two edges:
[
{ "source":"1.three", "target":"2.two" },
{ "source":"1.two", "target":"2.five" }
]
These edges are from a port on some vertex to a port on some other vertex, and it is the data-jtk-port attribute in our template that helps JsPlumb figure out which DOM elements to connect.
We call this a physical port mapping: for some port ID, there is a specific DOM element mapped to it.
Logical ports
What we call logical ports are slightly different - with this setup, there is no specific DOM element mapped to a given port, and edges connected to a logical port are shown in the UI as being connected to the port's vertex.
If you wish to use logical ports, you need to tell JsPlumb in the render options. JsPlumb assumes, by default, that you're not using logical ports.
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance()
const surface = toolkit.render(someElement, {
"logicalPorts": true
});
Here, we have two nodes that each contain a list of columns, and each column has an id, for instance:
{
id:"1",
left:50,
top:50,
columns:[
{id:"one", scope:"cadetblue"},
{id:"two", scope:"darkseagreen"},
{id:"three", scope:"cadetblue" }
]
}
We render each node this this template:
<div style="display:flex;flex-direction:column;>
<r-each in="columns">
<div data-jtk-scope="{{scope}}"
style="background-color:{{scope}}"
data-jtk-source="true"
data-jtk-target="true"
data-jtk-port-id="{{id}}">{{id}}</div>
</r-each>
</div>
In this example the key piece is the data-jtk-port-id attribute: it indicates to JsPlumb that that element is the logical representation of the given port on that vertex, meaning that any edges dragged from that element will be assigned a source port ID corresponding to the data-jtk-port-id attribute's value, but the actual DOM element used for the edge will be the vertex's DOM element.
In the dataset for the canvas below we have two edges:
[
{ "source":"1.three", "target":"2.two" },
{ "source":"1.two", "target":"2.five" }
]
These edges are from a port on some vertex to a port on some other vertex, and it is the data-jtk-port-id attribute in our template that helps JsPlumb figure out which DOM elements to connect.
We call this a logical port mapping: for some port ID, there is no specific DOM element mapped to it; JsPlumb uses the DOM element for the port's vertex.
Available attributes
This is the list of supported connectivity attributes:
| Attribute | Datatype | Description |
|---|---|---|
data-jtk-source | boolean | When present, indicates that the given element acts as a source for connections dragged with the mouse. Any element with this attribute set will automatically be excluded from instigating a drag of the vertex on which the element resides. |
data-jtk-target | boolean | When present, indicates that the given element acts as a target for connections dragged with the mouse. Elements with this attribute set are not excluded from instigating a drag of the vertex on which the element resides. |
data-jtk-port-id | string | This attribute indicates the ID of the logical port that the element represents. A logical port is one which exists in the data model, but connections to/from the port in the UI are shown as being attached to the vertex to which the port belongs. Don't confuse this with the data-jtk-port attribute. When you drag a connection to/from some DOM element with this attribute set, you are instructing JsPlumb to associate the source/target of the new edge with a port with the specified ID, but that the edge should be connected visually to the DOM element representing the vertex. |
data-jtk-port | string | This attribute indicates that the given element is the specific DOM element to which connections for the given port should be attached. It is distinct from data-jtk-port-id in that this attribute is a "physical" presence of a port. Connections to/from the associated port are attached to this DOM element, and not to the element representing the vertex to which the port belongs. |
data-jtk-port-type | string | This attribute maps to a port type in your view |
Constraining connectivity with edge scope
A high level approach to controlling connectivity is offered by the data-jtk-scope attribute. Edges dragged from some source element with a data-jtk-scope attribute can only be attached to target elements that have a matching data-jtk-scope. We use this approach in our Schema builder starter app.
This is the template we use to write out a DOM element that maps a table column:
<div class="jtk-schema-table-column" data-type="{{datatype}}"
data-primary-key="{{primaryKey}}" data-jtk-port="{{id}}"
data-jtk-scope="{{datatype}}" data-jtk-source="true" data-jtk-target="true">
<div class="jtk-schema-table-column-delete jtk-schema-delete"/>
<div><span>{{name}}</span></div>
<div class="jtk-schema-table-column-edit jtk-schema-edit"/>
</div>
Things to note:
- The
data-jtk-scopeattribute is set to thedatatypevalue in the column data data-jtk-sourceis set to "true", meaning this element is a connection sourcedata-jtk-targetis set to true, meaning this element is a connection target
Example
Here, we have two nodes that each contain a list of columns, and each column has a scope in its backing data - this is the data object for the node on the left:
{
id:"1",
left:50,
top:50,
columns:[
{id:"one", scope:"cadetblue"},
{id:"two", scope:"darkseagreen"},
{id:"three", scope:"cadetblue" }
]
}
We're using HTML colours for our scope so that we can style the elements easily, but scope is just an arbitrary string. We render each node this this template:
<div style="display:flex;flex-direction:column;>
<r-each in="columns">
<div data-jtk-scope="{{scope}}"
style="background-color:{{scope}}"
data-jtk-source="true"
data-jtk-target="true"
data-jtk-port="{{id}}">{{id}}</div>
</r-each>
</div>
Each of our column elements declares data-jtk-source and data-jtk-target to be true, meaning the element is both a source and target for edges dragged with the mouse. But the key piece is the data-jtk-scope attribute: try dragging an edge from a green to blue or vice verse below - you can't, due to a scope mismatch. But you can drag between elements having the same colour.
Constraining connectivity with interceptors
If edge scope is too high level for your needs, you can use interceptors, which provide a fine-grained means of controlling connectivity, at the model level.
The interceptors discussed here are passed as arguments to the underlying Toolkit.
Connectivity can be controlled at runtime by interceptors - callbacks that can be used to cancel some proposed activity, and that are bound on an instance of the Toolkit by supplying a specific function in the Toolkit constructor options. The Toolkit currently supports five interceptors.
beforeConnect
A function to run before an edge with the given data can be established between the given source and target. Returning false from this method aborts the connection. Note that this method fires regardless of the source of the new edge, meaning it will be called when loading data programmatically.
Method signature
beforeConnect(source: Vertex, target: Vertex): any
Parameters
- source The source vertex for the new edge
- target The target vertex for the new edge
Example
Here, we reject connections from any vertex to itself.
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeConnect": (source: Vertex, target: Vertex) => {
return (source !== target)
}
})
const surface = toolkit.render(someElement, {});
beforeMoveConnection
A function to run before an edge of the given type is relocated from its current source or target to a new source or target. Returning false from this method will abort the move.
Method signature
beforeMoveConnection(source: Vertex, target: Vertex, edge: Edge): any
Parameters
- source Candidate source. May be the edge's current source, or may be a new source.
- target Candidate target. May be the edge's current target, or may be a new target.
- edge The edge that is being moved.
The parameters source and target reflect the source and target of the edge if the move were to be accepted. So if, for example, your user drags a connection by its target and drops it elsewhere, target will be the drop target, not the edge's current target, but source will be the edge's current source. You can access the current source/target via the source and target properties of edge.
Example
Here, we reject moving any edge that has fixed:true in it backing data:
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeMoveConnection": (source: Vertex, target: Vertex, edge:Edge) => {
return (edge.data.fixed !== true)
}
})
const surface = toolkit.render(someElement, {});
beforeStartConnect
A function to run before an edge of the given type is dragged from the given source (ie. before the mouse starts moving). This interceptor is slightly different to the others in that it's not just a yes/no question: as with the other interceptors, returning false from this method will reject the action, that is in this case it will not allow a connection drag to begin. But you can also return an object from this method, and when you do that, the connection start is allowed, and the object you returned becomes the payload for the new edge.
Method signature
beforeStartConnect(source: Vertex, type: string): any
Parameters
- source The vertex that is the source for the new edge
- type The computed type for this new edge.
Example - reject a connection start
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeStartConnect": (source: Vertex, type:string) => {
return type !== 'not-connectable'
}
})
const surface = toolkit.render(someElement, {});
Example - provide an initial payload
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeStartConnect": (source: Vertex, type:string) => {
return {
type,
message:`initial payload for vertex ${source.id}`
}
}
})
const surface = toolkit.render(someElement, {});
beforeDetach
A function to run before the given edge is detached from the given source vertex. If this method returns false, the detach will be aborted.
Method signature
beforeDetach(source: Vertex, target: Vertex, edge: Edge, isDiscard?: boolean): any
Parameters
- source The source vertex for the edge that is to be detached.
- target The candidate target for the edge - may be null, if the edge is being discarded
- edge The edge that is being detached.
- isDiscard True if the edge is not now connected to a target.
Example
Here, we reject the detach if the target is null, ie. the user is trying to discard the edge, not relocate it.
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeDetach": (source: Vertex, target: Vertex, edge: Edge, isDiscard?: boolean) => {
return target != null
}
})
const surface = toolkit.render(someElement, {});
beforeStartDetach
Method signature
beforeStartDetach(source: Vertex, edge: Edge): any
A function to run before the given edge is detached from the given source vertex. If this method returns false, the detach will be aborted. The difference between this and beforeDetach is that this method is fired as soon as a user tries to detach an edge from an endpoint in the UI, whereas beforeDetach allows a user to detach the edge in the UI.
Parameters
- source The source vertex for the edge that the user has started to detach
- edge The edge that the user has started to detach
Example
Here, we reject the detach if the source vertex has doNotDetachEdges:true in its backing data.
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeDetach": (source: Vertex, edge: Edge) => {
return source.data.doNotDetachEdges !== true
}
})
const surface = toolkit.render(someElement, {});
Example
In this example we provide a beforeStartConnect and beforeDetach interceptor to an instance of the Toolkit. The beforeStartConnect interceptor prevents the user from dragging connections from any vertex whose ID is not an even number. The beforeDetach interceptor reattaches detached connections whose source ID is not an event number
import { newInstance } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeStartConnect": (source, type) => {
// only allow connections from nodes whose
// ID is an even number
return parseInt(source.id, 10) % 2 === 0
},
"beforeDetach": (source, target, edge, isDiscard) => {
// only allow connections to be detached whose
// source ID is an even number
return parseInt(edge.source.id, 10) % 2 === 0
}
})
const surface = toolkit.render(someElement, {});
Active filtering
You can use the Active filtering plugin in conjunction with a beforeConnect interceptor to implement a scheme where unavailable targets are disabled when the user starts to drag a new connection.
import { newInstance,
ActiveFilteringPlugin } from "@jsplumbtoolkit/browser-ui"
const toolkit = newInstance({
"beforeConnect": (source:Vertex, target:Vertex) => {
return source.data.scope === target.data.scope
}
})
const surface = toolkit.render(someElement, {
"plugins": [
ActiveFilteringPlugin.type
]
});
Advanced Markup Configuration
The previous section lists the most commonly used attributes, but there are several more that can be used in more advanced configurations, specifically when you want to use a different physical or logical port mapping depending on whether the connection is to an edge or target.
| Attribute | Datatype | Description |
|---|---|---|
data-jtk-source-port-id | string | Indicates the ID of the logical port that the element represents when it is acting as an edge source. In some situations you might want to use the same element as a source and target, but have the data model use different logical ports. |
data-jtk-target-port-id | string | Indicates the ID of the logical port that the element represents when it is acting as an edge target. |
data-jtk-source-port | string | Indicates the ID of the physical port that the element represents when it is acting as an edge source. In some situations you might want to use the same element as a source and target, but have the data model use different ports ids. |
data-jtk-target-port | string | Indicates the ID of the port that the element represents when it is acting as an edge target. |
data-jtk-source-port-type | string | Indicates the type of the port that the element represents when it is acting as an edge source. In some situations you might want to use the same element as a source and target, but have the data model use different ports types. |
data-jtk-target-port-type | string | Indicates the type of the port that the element represents when it is acting as an edge target. In some situations you might want to use the same element as a source and target, but have the data model use different ports types. |