UI / Templating

Templating

One great feature of the jsPlumb Toolkit is the way client side templating is baked right in - your Node, Group and Port definitions in the View declare the id of some template to use and the Toolkit takes care of the rest.

Knockle is the default template adapter in the jsPlumb Toolkit. You can use any templating mechanism you want to - you just need to write an appropriate adapter, and provide this function as the value of the templateRenderer parameter - but it is recommended that you use Knockle, because Knockle's ability to update previously rendered content means that you can update your UI "automatically" from a change to the data.

Note : if you are using the Angular, Vue or React integration, then Knockle is not used for your templates. You'll be writing components for that library and using its own template syntax.

Here's what the table template looks like in the Database Visualizer application:

<script type="jtk" id="tmplTable">
  <div class="table">
    <div class="table-name">
      ${name}
      <div class="new-column" title="Click to add a new column">+</div>
    </div>
    <ul class="table-columns">
      <r-each in="columns">
        <r-tmpl id="tmplColumn"></r-tmpl>
      </r-each>             
    </ul>
  </div>
</script>

and here's tmplColumn:

<script type="jtk" id="tmplColumn">
  <li class="table-column table-column-type-${type} {{if primaryKey}}table-column-primary-key{{/if}}">
    <div class="table-column-delete" data-port-id="${id}">x</div>
    ${id}
    <jtk-source port-id="${id}" port-type="column" scope="${datatype}"></jtk-source>
    <jtk-target port-id="${id}" port-type="column" scope="${datatype}"></jtk-target>
  </li>
</script>

Notice any non-standard HTML in there? The jsPlumb Toolkit supports declarative source/target/endpoint configuration, discussed in Connectivity below.

These templates are matched to Node/Port types in the Database Visualizer's view. Here's an edited snippet of the view:

nodes: {
  "table": {
    template: "tmplTable"
  }
  ...
},
...
ports: {
  "default": {
    template: "tmplColumn",
    ...
  }
}

When using the default template engine, your templates must return a single root node. If you return multiple nodes, the Toolkit will use the first node only.


As shown above, the Node and Port definitions passed as part of the view parameter on a render call allow you to specify a template to be used to render an instance of the given type. Sometimes you will find that you have nothing other than a template directive in a Node or Port definition. So to cut down on boilerplate, the Toolkit will make a guess at a suitable template id if you do not provide.

The inferred template id consists of "jtk-template-" as a prefix, followed by the type of the object. So in the previous example, a Node of type table would have an inferred template id of jtk-template-table.


The default template resolution mechanism is to look for a <script> element in the DOM with an ID matching the ID of the template to retrieve. You can supply your own template resolver like this:

toolkit.render({
  container:...,
  view:{ ... },
  templateResolver:function(templateId) {
    // find the matching template and return it - as a String.
  }
});

Another option is to pass a map of templates into the render call:

toolkit.render({
  container:...,
  view:{ ... },
  templates:{
    type1:"<div><span>type 1</span>...</div>",
    type2:"<div><span>type 2</span>...</div>",
  }
});

For some use cases, where the templates are not overly complex, this can be handy. One caveat to note with this approach is that Knockle expects double quotes for attributes, so if your template has any attributes declared you need to use escaped double quotes:

toolkit.render({
  container:...,
  view:{ ... },
  templates:{
    type1:"<div class=\"foo\"><span>type 1</span>...</div>",
    type2:"<div><span>type 2</span>...</div>",
  }
});

NOTE This section applies both to the case that you are using Knockle for your templates, or you are using one of the library integrations. One thing to watch out for, though, is that when using Vue you need to instruct Vue to ignore these elements via the inclusion of a v-pre attribute, for example:

<jtk-source v-pre port-id="success" port-type="output" filter=".someCssSelector"></jtk-source>

jtk- Elements

To configure your UI to allow for the user to establish connections using the mouse, you use one or more custom tags in your HTML templates. In the previous section, we saw a template that had a jtk-source and a jtk-target element; these were used to declare that the given element should act as both a connection source and connection target.

A third custom element available in the jsPlumb Toolkit is jtk-port - used to declare an Endpoint on some element.

A full discussion of each element follows.

jtk-source

This element is used when you want to configure some element as a connection source. It is analogous (and maps directly to) the makeSource method in jsPlumb. The element that is configured as the connection source is that element which is the parent of the jtk-source element - here, it's the .table-column li.

<jtk-source port-id="success" port-type="output" filter=".someCssSelector"></jtk-source>

Six attributes are supported on the jtk-source element:

  • port-id

This defines the id of the port on the node. It is not mandatory: if you use the jtk-source element to turn some Node into a connection source, then you don't need to set this attribute - you are configuring the Node's default Port. If you wish to assign this connection source to a Port, though, then you must set this attribute. This attribute's value must be unique on its Node, but may be the same as the id of some Port on another Node. Here we are using the id member of the data backing the column.

  • port-type

Maps to a Port type in the view, as discussed above. Note that you can set this even if you don't set port-id, as you will want to associate a Node's default Port with some type.

  • scope

The jsPlumb Toolkit can make use of jsPlumb's scope concept to control, in a relatively crude way, what can be connected to what. In this template you see that both the jtk-source and jtk-target elements use the same value for scope, and in the Database Visualizer this is mapped to the column's underlying datatype. There is a more sophisticated mechanism available to control connections should you need it: see Interceptors.

  • endpoint

Setting this to true causes jsPlumb to create an Endpoint for the given Node, and not to configure the element itself as a drag source. The created Endpoint is used for any Edges whose source is the given Node. New Edges may be dragged from the Endpoint.

  • filter

Oftentimes you will want to configure an element as a connection source but not actually have the entire element respond to a connection drag start. This is quite common in the sorts of UIs for which the jsPlumb Toolkit is used: you need to be able to drag your nodes around the screen, but you'd also like to be able to drag connections from the nodes too. Using filter you provide a CSS selector that identifies elements from which connection drag should be supported.

  • filter-exclude

If you run into a problem specifying a suitable selector for the filter (:not selectors, in particular, are a little restricting), you can set filter-exclude:true, which will mean that the elements identified by the filter selector will be excluded from starting a drag.

jtk-target

This element is the opposite of the jtk-source element: it allows you to identify its parent as an element that you wish to configure as a connection target. It also has an analogue in jsPlumb to which it is directly mapped: makeTarget.

This element supports four of the six attributes supported by jtk-source. Their meaning and usage is the same:

  • port-id
  • port-type
  • scope
  • endpoint

The fifth and six parameters - filter and filter-negate - are not supported, which means that it is always the entire element that is configured as a connection target. It is possible that, in the future, jsPlumb will support the filter and filter-negate attributes on the makeTarget method. If/when that happens, the Toolkit's jtk-target element will be updated to also support them.

jtk-port

Use this element when you want to have an Endpoint added to your UI and map it to a Port. Its analogue in jsPlumb is the addEndpoint method. This element supports the same attributes as makeTarget (with the exception of endpoint), plus some extras:

  • port-id

  • port-type

  • scope

  • anchor-x This attribute can be used to specify the location, in the x axis, of the Anchor used by the Endpoint associated with this Port. This is a proportional value, as discussed in the anchor documentation.

  • anchor-y This attribute can be used to specify the location, in the y axis, of the Anchor.

  • orientation-x Used to associate an orientation, in the x axis, with the Anchor associated with the Port. Orientation is discussed in the anchor documentation linked above. This is optional, but if omitted, the default of 0 will be used.

  • orientation-y Used to associate an orientation, in the y axis, with the Anchor associated with the Port.

Note using this declarative means of configuring ports does not mean that the UI artefacts associated with the Endpoints exist in the DOM as children of the port's parent element. Remember that the Toolkit always makes use of jsPlumb's Container concept, to ensure that all of the Endpoints and Connections contained inside a given Surface have the same parent.

Note also that any jsPlumb Toolkit elements that are found during rendering are removed from the DOM after processing.


To help you organize your templates better, you can store them in external files and reference them via a script import in your HTML:

<script type="text/x-jtk-templates" src="../palettes/shapes-svg.html"></script>

The key here is that the type is text/x-jtk-templates. There may be one or more templates declared in the specified file. This example is one from one of the Toolkit's unit tests; the file contains several templates. Here's the first two:

<script type="jtk" id="tmplRectangle">  
  <svg:svg style="position:absolute;left:0;top:0;">
    <svg:rect width="${width}" height="${height}" x="${strokeWidth}" y="${strokeWidth}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" {{if rotate}}transform="rotate(${rotate} ${(width / 2) + strokeWidth} ${(height/2) + strokeWidth})"{{/if}}></svg:rect>
  </svg:svg>    
</script>

<script type="jtk" id="tmplCircle"> 
  <svg:svg style="position:absolute;left:0;top:0;">
    <svg:circle class="jtk-svg" cx="${cx}" cy="${cy}" r="${r}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}"></svg:circle>
    <jtk-port port-id="port" port-type="basic"></jtk-port>
  </svg:svg>    
</script>

You can have multiple x-jtk-templates scripts declared in your html. Note, though, that the loading of these scripts postpones the firing of the ready event in the Toolkit, so you should ensure you're not getting any 404s and what you are loading is loading quickly.


Knockle is the default templating mechanism used by the jsPlumb Toolkit. It uses a strict XHTML syntax and can run both in the browser and headless on the server.

These are the key points:

  • Format is strict XHTML: all tags must be closed. This means
<input type="text"></input>

for example. The only exception to this rule is the <r-else> element:

<r-if test="size > 10">
   <h1>large</h1>
<r-else>
   <h6>small</h6>
</r-if>
  • Use only double quotes for attributes:
<div class="foo"></div>

not

<div class='foo'></div>

Inside attribute values, however, you can use single quotes:

<r-if test="value == 'foo'">...</r-if>

Tags

Each
With Objects in an Array
{
  someDataMember:[
    { id:"one", label:"value1" },
    { id:"two", label:"value2" }
  ]
<ul>
    <r-each in="someDataMember">
        <li id="${id}">${label}</li>
    </r-each>
</ul>    
With Arrays in an Array
{
  someDataMember:[
    [ "one", "value1" ],
    [ "two", "value2" ]
  ]
<ul>
    <r-each in="someDataMember">
        <li id="${$data[0]}">${$data[1]}</li>
    </r-each>
</ul>    

The key here is that the current array is exposed as the variable $data.

With an Object
{
  someData : {
    id:"foo",
    label:"FOO is the label",
    active:true,
    count:14
  }
<table>
  <r-each in="someData">
    <tr><td>${$key}</td><td>${$value}</td></tr>
  </r-each>
</table>

The key here is that each entry is presented to the template as an object with $key and $value members.

Uniquely identifying child nodes

In some cases, such as the Database Visualizer application that ships with the Toolkit, you might want to use the r-each element to loop through your data and add ports to your nodes. For instance:

<ul class="table-columns">
    <r-each in="columns" key="id">
        <r-tmpl id="tmplColumn"/>
    </r-each>
</ul>

This is how the database visualizer application renders the columns in a table. tmplColumn looks kind of like this:

<script type="jtk" id="tmplColumn">
  <li class="table-column table-column-type-${datatype}" primary-key="${primaryKey}" data-port-id="${id}">
    
    ...
    
    <jtk-source port-id="${id}" port-type="column" scope="${datatype}" filter=".table-column-delete, .table-column-delete-icon, span, .table-column-edit, .table-column-edit-icon" filter-exclude="true"></jtk-source>
    <jtk-target port-id="${id}" port-type="column" scope="${datatype}"></jtk-target>
    
  </li>
</script>

with some details removed for brevity. The thing to note is that this template, which is looped over, generates ports for the node. Note the key attribute on the r-each element above. It instructs Knockle on how to get a unique identifier for each value in the loop. Thus, if you change the data in some port, Knockle will just update the port's element, since it can use the key property to identify the existing element. Similarly, if you add a new port, Knockle will use the key to determine that it has no current element, and if you remove a port, Knockle will use the key to determine that the element corresponding to that port is no longer needed, and will remove it.

Knockle will log a message to the console any time the r-each element is used without a key. It isn't an error, but in many cases it's likely you'll want to provide the key for each child.

If

There are two if statements in Knockle: one that is an element, which you use in the body of your templates, and one that is inline, which you use inside tags to selectively include/exclude attributes:

Existence
<r-if test="someObjectRef">
    <div>hola</div>
</r-if>
Expressions
<r-if test="foo == 5">
    <div>hola</div>
</r-if>
Inline
<input type="radio" class="foo"{{if selected}} selected{{/if}}></input>

Notes

  • you can not use the IF statement inside an attribute expression.
Else

The element version of the IF statement has an optional ELSE statement:

<r-if test="something">
  <div class="success">ok</div>
<r-else>
  <div class="fail">things are not ok.</div>
</r-if>
Comments

Comments follow the standard XHTML syntax:

<div>
<!--
    a comment
    <span>Maybe some code was commented</span>
-->
</div>

Comments are stored in the parse tree for a template. This may or may not prove useful.

Embedding HTML

By default Knockle treats text as plain text. For example with this template:

<script type="jtk" id="tmplExample"> 
    <div>
        <span>${text}</span>
    </div>
</script>

and this call:

var el = Knockle.template("tmplExample", { text:"<h1>Hello</h1>" });

The innerHTML of the span would be the string "<h1>Hello</h1>".

You can use the r-html tag to indicate that you're expecting HTML:

<script type="jtk" id="tmplExample"> 
    <div>
        <span><r-html>${text}</r-html></span>
    </div>
</script>

Now you'll get a span with an h1 child element:

<div>
    <span>
        <h1>Hello</h1>
    </span>
</div>
Nested Templates
With specific context
<div>
  <r-tmpl id="nested" context="someItem"></r-tmpl>
</div>
Inheriting parent context
<div>
  <r-tmpl id="nested"></r-tmpl>
</div>

The difference between these two examples is that in the first, an item called someItem is extracted from the current dataset, and passed in to the nested template, whereas in the second, the nested template is passed the exact same data that the parent is currently using to render itself.

Inside an r-each loop
<div>
    <r-each in="someList">
        <r-tmpl id="nested"></r-tmpl>
    </r-each>
</div>

This is similar to the example immediately above - the nested template inherits its parent's context, but in this case the parent's context is currently some item from the list. You can also use context in this situation:

<div>
    <r-each in="someList">
        <r-tmpl id="nested" context="someMemberOfTheListItem"></r-tmpl>
    </r-each>
</div>

The context for the nested element here is the someMemberOfTheListItem member of each list item.

With complex context

You are not limited to extracting single variables from the current context to pass in to a nested template. You can specify a complex object too:

<div>
  <r-tmpl id="nested" context="{id:foo, label:'Hello'}"/>
</div>

In this example, foo will be extracted from the context in which the current template is executing, and Hello is a hardcoded string.

####### Accessing nested properties

You can also specify properties that are nested inside the current context, either with dotted notation:

<div>
  <r-tmpl id="nested" context="{id:record.id, label:'Hello'}"/>
</div>

or by naming the property:

<div>
  <r-tmpl id="nested" context="{id:record['id'], label:'Hello'}"/>
</div>
Dynamic Template Names

You can lookup the name of a nested template at runtime, for example consider these templates:

<script type="jtk" id="someTemplate">
    <h3>${title}</h3>
    <r-tmpl lookup="${nestedId}" default="def"/>
</script>

<script type="jtk" id="green">
    <h3>GREEN</h3>
</script>

Here we see the ID of the nested template is derived from the nestedId property of the data we are rendering:

{
    title:"example",
    nestedId:"green"
}

default allows you to provide the ID of a template to use if the lookup fails.

Note that with lookup you can use arbitrary Javascript, as you can elsewhere in Knockle. So you could instead say something like:

 <script type="jtk" id="someTemplate">
     <h3>${title}</h3>
     <r-tmpl lookup="${lookupTemplate(nestedId)}" default="def"/>
 </script>

To render SVG elements you must prefix the tag with a namespace:

<svg:svg width="50" height="50">
  <svg:rect x="10" y="10" width="10" height="10"></svg:rect>
</svg:svg>

Updating Data

Given this template for some node type:

<div class="someNode">
    <span>${title}</span>
    <ul>    
        <r-each in="someDataMember">
            <li>${id}</li>
        </r-each>
    </ul>
</div>

and this call to a Toolkit instance:

var node = toolkit.addNode({
  title:"FOO",
  someDataMember:[
    { id:"one" },
    { id:"two" },
    { id:"three" }
  ]
});

You'll get a node element with a span that says FOO, and a list of three items: one, two and three.

Calling updateNode on the Toolkit instance associated with this node:

toolkit.updateNode(node, {
   title:"FOO-NEW",
   someDataMember:[
       { id:"un" },
       { id:"deux" },
       { id:"trois" }
   ]
});

will result in a node element with a span that says FOO-NEW, and a list of three items: un, deux and trois.

Updating the class attribute

Knockle won't update the class attribute once a template has been written. Since the update method can only write values for classes that were in the template, there's a risk that any classes added by other parts of your app would be removed. For example say you have this template:

<div class="${nodeType}">
  FOO
</div>

If you render this with {nodeType:"start-node"} then you'd end up with a div with class start-node. Then say some code comes along and does this:

$(myDiv).addClass("selected");

Now you've got a div with class start-node selected. If you then called update, Knockle would re-write the class attribute to have only the nodeType class; probably not at all what you want. In this scenario you are better off using attribute selectors.


You can provide your own template renderer to replace Knockle, should you wish to, by supplying it as the templateRenderer argument to a render call on an instance of the Toolkit.

Note as mentioned above, it is recommended that you use Knockle, because Knockle's ability to update previously rendered content means that you can update your UI "automatically" from a change to the data. Using some other template renderer will mean that the updateNode, updatePort and updateGroup methods cannot automatically update your UI.

var surface = toolkitInstance.render({

    ...
    templateRenderer:{
        render: function(templateId:string, data:object, toolkit:jsPlumbToolkitInstance, objectType:string, surface:Surface) {
                        
            // return a DOM element
        },
        cleanup: function(objectId, element) {
            // do any cleanup required, remove the element from the dom.
        }
    },
    
    ...

});

templateRenderer is required to supply two functions:

render

The method parameters are:

  • templateId The ID of the template to render. This will have been derived from the mapping of the node/group/port type in your view.
  • data The backing data for the Node/Group/Port being rendered - this is the data you have passed in to the Toolkit.
  • toolkit The associated Toolkit instance.
  • objectType One of node, group or port. Useful in certain situations.
  • surface The surface into which the template is being rendered.

This method is expected to return a single DOM element.

cleanup

  • objectId The id of the object being removed
  • element The DOM element for the object being removed. This is the same object you returned from render.