Rotors
Rotors is the default templating mechanism used by the jsPlumb Toolkit. It began life as a port of an early version of jQuery templates, from which the jQuery dependency was removed. Then various changes were made, not the least of which was the addition of support for updating rendered templates, and a few method names were changed and some syntax revised. At that point, in the core of Rotors you could still find the original jQuery templates code, but since that time a complete, ground-up rewrite has occurred, and Rotors now has no traces of jQuery templates at all.
Rotors uses a strict XHTML syntax and can run both in the browser and headless on the server.
Quick Start Guide
These are the key points:
- You need to get an instance of Rotors before you do anything. All of the examples in this guide will assume you first ran this line of code:
var rotors = Rotors.newInstance();
-
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. (I know I said all tags. I just wanted to impress upon you the importance of thinking about strict XHTML before admitting there was an exception). -
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>
Rendering
var rotors = Rotors;var fragment = rotors;
Here, fragment
is, when running in a browser, a DocumentFragment
. When running on the server, it is a Fakement
- an internal
Rotors class that behaves like a DocumentFragment
.
One important piece of the rendering process that the above call does not make clear is that Rotors needs some way of looking up
template content from the template IDs you give it. By default, in a browser, Rotors will look for an element with the given ID,
and then use that element's innerHTML
. On the server there is currently no default behaviour, and so you have to use the
three argument version of the template
method:
var rotors = Rotors;var fragment = rotors;
Template Resolver
By default, Rotors (when running in a browser), will attempt to resolve templates by looking for a script
element with
the same ID as the ID of the request template:
{ return document;}
When you make a call like this:
var rotors = Rotors;var fragment = rotors;
Rotors therefore looks for a script element with ID someId
. This can, however, be overridden, by providing your own
template resolver:
var rotors = Rotors;var fragment = rotors;
Instance wide template resolver
You can also override the template resolved in the parameters you pass to Rotors.newInstance
:
var rotors = Rotors;
Providing templates directly
A further option for template resolution is to provide a map of templates to the Rotors.newInstance
method
var rotors = Rotors;
Default Template
You can set a default template to use if all other template resolution fails:
rotors.setDefaultTemplate("<div id=\"${id}\"></div>");
This can subsequently be cleared:
rotors.clearDefaultTemplate();
But note that any template that was rendered while the default template was available will still be in the cache, eg:
rotors.setDefaultTemplate("<div id=\"${id}\"></div>");
var e = rotors.template("someTemplateId", data);
rotors.clearDefaultTemplate();
var e2 = rotors.template("someTemplateId", data);
Here, both e
and e2
will be a DocumentFragment
containing a div
element. If you need to clear out anything that was
compiled with the default template, you'll also need to call clearCache()
.
Tags
Each
With Objects in an Array
someDataMember: id:"one" label:"value1" id:"two" label:"value2"
${label}
With Arrays in an Array
someDataMember: "one" "value1" "two" "value2"
${$data[1]}
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
${$key}${$value}
The key here is that each entry is presented to the template as an object with $key
and $value
members.
If
There are two if
statements in Rotors: 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
hola
Inline
Note you can not use the IF statement inside an attribute expression.
Else
The element version of the IF statement has an optional ELSE statement:
ok things are not ok.
For
The r-for
tag takes a loop
attribute that specifies how many iterations you want. It can be a static value:
${$index}
..or it can be a computed value (from the data that is in scope at the time the for
loop executes):
${$index}
You might, for instance, have called the template that renders the for
loop above with this data:
somelist:01234
The current loop index is available as the $index
parameter inside the template.
Comments
Comments follow the standard XHTML syntax:
<!-- a comment <span>Maybe some code was commented</span>-->
Comments are stored in the parse tree for a template. This may or may not prove useful.
Embedding HTML
By default Rotors treats text as plain text. For example with this template:
${text}
and this call:
var el = Rotors;
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:
${text}
Now you'll get a span
with an h1
child element:
Hello
Nested Templates
With specific context
Inheriting parent context
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.
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:
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:
or by naming the property:
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 Rotors. So you could instead say
something like:
<script type="jtk" id="someTemplate"> <h3>$title</h3> <r-tmpl lookup="${lookupTemplate(nestedId)}" default="def"/> </script>
Rendering SVG
To render SVG elements you must prefix the tag with a namespace:
Updating Data
Given this template:
${title} ${id}
If you render it with this call:
var fragment = rotors.template("theTemplateId", { title:"FOO", someDataMember:[ { id:"one" }, { id:"two" }, { id:"three" } ]});
You'll get a span
that says FOO
, and a list of three items: one
, two
and three
.
You can update from the root node:
rotors
You now have a span
that says FOO-NEW
, and a list of three items: un
, deux
and trois
.
You can also update one specific element, you just need to ensure that the data you give it is in the appropriate format.
Let's take the second li
element and update it:
var li2 = fragment1;rotors;
Now the second list item is speaking Castellano and everyone else is speaking French.
class
attribute
Updating the Rotors 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:
FOO
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:
;
Now you've got a div
with class start-node selected
. If you then called update
, Rotors 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.
Update Notifications
You can get a notification about updates with the onUpdate
method:
rotors;
Excluding Elements
It is possible to exclude elements from being compiled into a template, via a toggleManager
. This is a function that takes the name of some toggle as a String, and
returns boolean false if that toggle is not "enabled", whatever that means in your context. An example:
rotors;
Obviously this is a spurious example. In the real world it is likely you'd have this wired up to the config of your application somehow.
To depend on a toggle in your markup:
Lorem Ipsum ... Some Experiment
Here, if your toggle manager reports that experiments.on
is false, then the entire div with class experimentalFeature
and all of its descendants will be excluded from template before it is compiled.