Dust Tutorial GitHub
Dust Tutorial GitHub
1 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Dust Tutorial
Table of Contents
Dust Tutorial
Why JavaScript Templating?
Show me some Dust
More on Dust Output and Dust Filters
Dust Data and Referencing
Sections
Sections and Context
Paths
New path behavior available as of dust 2.0.0 release
Explicit context setting
Sections with parameters
Parameter rules
Logic in Templates
Partials
Dynamic Partials for Logic
Helpers
Logic Helpers
{@select key="xxx"} + @eq, @ne, @lt, @lte, @gt, @gte, @default, Available in Dust 1.0
{@math} - math helper, Available in Dust 1.1 release
@math with bodies
{@if cond="condition"} - if helper, Available in Dust 1.0 release
Other Helpers
{@sep} - Separator helper, Available in Dust V1.0 release
{@idx} - Index helper, Available in Dust V1.0 release
{@size key="xxx" } - size helper, Available in Dust V1.1 release
{@contextDump key="current|full" to="output|console"/} - contextDump helper, Available in Dust V1.1
release
Blocks and Inline Partials
Dust under the covers
Compiling a Dust template
Running a Dust Template
Debugging a Dust Template
Setting your own context before render
Controlling whitespace suppression
Writing a dust helper
External Support for dust usage
Loading dust in the browser
Dust Tutorial
Richard Ragan - PayPal ( Author )
Veena Basavaraj - LinkedIn ( Edits )
11/21/2014 5:30 AM
2 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
In addition to being cacheable, the Dust templates compiled to JavaScript can be served from a CDN for faster loading
No need for UI developers to start a Tomcat server to render a page served with JSON-- much faster cycle time, less issues
Continuing acceleration of JavaScript performance in browsers continues to make client-side work faster
Write once and can run the same on both the client (browser) and server using JS engine such as V8/rhino
JSON:
{
"title": "Famous People",
"names" : [{ "name": "Larry" },{ "name": "Curly" },{ "name": "Moe" }]
}
Dust templates output plain old text and processes dust tags -- {xxxxxx} being a dust tag format. The tag structure is similar to
html in general form but using braces instead of <>, e.g. {name /}, {name}body inside tag{/name} and with parameters {name
param="val1" param2="val",... }.
The simplest form is just
{name}
and is called a key. It references a value from the JSON data named "name". In our example, you saw the key {title} which
produced an output value of "Famous People". The other tag form we saw in the example was
{#names}....{/names}
11/21/2014 5:30 AM
3 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Sections
A section is a Dust tag of the form {#names}...{/names}. It is the way you loop over data in Dust. What happens is the current
context (more on context shortly) is set to the "names" part of your JSON data. If names is an array, the body wrapped by the
section tag is executed for each element of the array. If the element is not an array, the body will just be executed once. If the
"names" element does not exist or is empty, the body will be skipped.
During the execution of the section iteration, two variables are defined: $idx - the index of the current iteration starting with zero
and $len - the number of elements in the data being iterated.
Note: $idx and $len work for arrays of primitive types as of the 1.1 release.
11/21/2014 5:30 AM
4 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
"name":"Albert",
"B":{
"name":"Bob"
}
}
}
As we learned earlier, if you have {#A}{name}{/A} the current context is A: and everything under it (i.e. it includes the B: stuff). The
key for {name} will output "Albert" because that is the direct value of name in the context of "A".
So how does it work if you have {#A}{anotherName}{/A}? You will get "root2" as the output. That's because "anotherName" could
not be found directly under "A" so Dust tries to walk up to the parent of "A" (which is the root context in our case) and finds
"anotherName" hence using its value. In general, a simple key reference will look first in the current context and, if not found,
search all higher levels up to the root looking for the name. It will not search downward into things like "B" that are nested within
"A".
Paths
Suppose our context is the root and say we want to work with the data "only" under "B". Like in JavaScript itself, you can use a
dotted notation called a "path" to do this. For example, {A.B.name} will output "Bob".
Simple key references like {A.B.name} are sometimes not enough. You might need to use a section to iterate over a sub-part of
the JSON model. Remember when you use {#xxxx} for a section, you also establish a new context at that point. For example, in
the case of {#A.B}{name}{/A.B}, This will output "Bob" because our context has been set to B within A. Path notation only allows
you to reference a path visible within the current context.
You CANNOT reference a value using paths when that pathed value is outside your current context. Lets look at an example to
make this point clear.
{#A.B}
name in B={name} name in A= {A.name}
{/A.B}
The above will output "name in B=Bob name in A=" showing that A.name is not accessible inside the context A.B. What goes on
is that dust looks for the initial part of the path in the current context, e.g. A, and gives up when it cannot find it.
There is a way out of this behavior. Follow along with the below dust fragment. {#A}, is a non-pathed reference so it is allowed to
search upward and find "A". Then {#A} sets a new context to "A" allowing us to reference the "name" value under "A". When the
closing tag {/A} is reached, the context reverts to {#A.B}, Yes, the context acts like a stack.
IMPORTANT: While you cannot use a dotted path notation to reference ancestor/parents JSON from the current context, you can
use a non-pathed section reference to adjust your context to a higher point. For example,
{#A.B} name in B={name}
{#A}
name in A: {name}
{/A}
{/A.B}
Another way to reference a value outside your current context is to pass the value into the section as an inline parameter (we will
talk more about parameters soon). Here is an example of how to access A.name within the {#A.B} context using a parameter on
the {#A.B} section
{#A.B param=A.name}
name in B={name} name in A: {param}
{/A.B}
11/21/2014 5:30 AM
5 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
So what does the change do? If you look at the example above (using the same data):
{#A.B}
name in B={name} name in A={A.name}
{/A.B}
with this path change it will output "name in B=Bob name in A=Albert". The new rules for resolving path references are as follows:
If the path is {x.y.z} and x is not in the current context, begin looking in outer contexts for x. If found, look from there for y.z. If
found, return the value. If not found, give up and return no value. Do not look further.
If the path is {x.y.z} and x is not found in any context, then look for the path in globals similar to the way simple key references
look in globals as a final search location. The difference is that paths can now be found in globals.
Some things remain unchanged from the original dust:
If the path starts with a period, e.g. {.x.y.z}, the search remains restricted to the current context.
If the path is {x.y.z} and the entire path can be found in the current context, the value of the path will be used.
If an explicit context (covered in next section) is established by {name:context}, then only the context determined by name
and that provided by :context are visible. You cannot escape out of these two contexts and reference anything further up the
stack. This is like the original dust.
This change eliminates the need for using a {#x} sort of hack to reach a path that is in an outer context.
For those wondering about compatibility implications, they should be slight. The two main cases are:
A path reference that used to return no value, might start returning a value if the path can be found in an outer context of the
data. The fix is to prefix the path with a period to constrain the search to the current context.
The code might test for the existence of data using a path to determine whether to output some conditional text. Presence or
absence of the path in the data used to control this behavior. Now if the path is omitted but it can be found in outer context,
the output that used to be omitted will start appearing. Like the previous case, the different output relies on the path actually
appearing somewhere in an outer context. As in the first case, adding a leading period will constrain the search to just the
current context restoring the old behavior.
will do that.
Specifically it does the folllowing:
Hides all nested context levels above "name"
Puts "name2" data as the parent context and name as the current context
This prevents {key} references from accessing any data not in the name or name2 contexts. No further reaching up can happen
even with simple key forms like {name}. This scope limitation might be useful for data hiding from components. Another use for it
could be to make only the current context and it's peer available.
Given a data model where A and B are peers and we need to iterate over A and also reference data from B, without explicit
context setting we would have trouble doing this.
{
"A":
{names:
["Albert", "Alan"]
},
"A2":{
"type":"Student"
}
}
11/21/2014 5:30 AM
6 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
will output "Albert - Student Alan - Student since both A and A2 are on the context stack even though A2 would not normally be
there.
This will output "Albert Bob root2". It's important to understand the context at the point the parameter values are established. With
foo=A.name above, A.name is evaluated before the context is moved to A.B, thus A.name is accessible.
However, if the parameter values are interpolated into strings, they are evaluated in the context of the section using them.
Therefore, the following will just output "Bob root2" because {A.name} is not accessible from the {#A.B} context.
{#A.B foo="{A.name}" bar="{anotherName}" }
{foo} {name} {bar}
{/A.B}
you cannot do anything useful with it since the {foo.name} reference is going to look for foo in the current context but that context
is the element of the current iteration of the section #A.B (in this case just the name: "Bob", value). Therefore, "foo" won't be
found. The foo parameter is on the context stack but one level higher than the current element iteration so unreachable by a path
reference.
When deciding on parameter names, try to be unique. Inline parameters will not override the current context if a property of the
same name exists. Let's look at an example:
{#A name="Not Albert"}
name is {name}.
{/A}
will output "name is Albert" since preference goes to data in the current context followed by inline parameters then up the context
tree.
If we want to be sure we get the value in the parameter we can make it unique.
{#A paramName="Not Albert"}
name is {paramName} and {B.name} is still Bob.
{/A}
11/21/2014 5:30 AM
7 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Parameter rules
Since parameters are on your mind, let's discuss the three forms parameters can take.
param=name
param="xxx"
param="{yyy}"
In the first form, name is obtained from the context. It can be a path. In the second form, the value looks like a string and is, in
fact, a string value. In the third form the value for the name yyy is obtained from the context and interpolated into the string
resulting in a string value.
One more subtle nuance is when the value of the parameter is determined. With the first form param=name, value of name is
obtained from the context before the section (or partial or helper) is processed/invoked. Ditto for the second form which is a string
constant. The third form is the tricky one. Dust only evaluates the interpolated string within the section/partial/helper. Mostly this
does not matter but if a new value for yyy is added to the context stack via another param of the same section/partial/helper (i.e.
{#A.B param="{yyy}" yyy="baz"}), that value will be found and used rather than the one known at the point of call. Be careful
naming parameters the same as data you are referencing from the context.
Logic in Templates
Templates with logic versus "logic-less" templates is a hotly debated point among template language designer and users. Dust
straddles the divide by adopting a "less logic" stance. We all know that real business logic does not belong in the presentation
layer, but what about simple presentation-oriented things like coloring alternate rows in table or marking the selected option in a
<select> dropdown? It seems equally wrong to ask the controller/business logic code to compute these down to simple booleans
in order to reduce the logic in the presentation template. This route just lead to polluting the business layer code with
presentation-oriented logic.
Dust provides some simple logic mechanisms and trusts you to be sensible in minimizing the logic in your templates to only deal
with presentation-oriented decisions. That said, let's take a look at the ways Dust let's you have logic.
There are two other special section notations that provide conditional testing:
{?name} body {/name}
not only tests the *existence* of name in the current context, but also evaluates the value of name in the JSON model. If
name is "true" (see below for what true means), the body is processed.
{^name} body {/name}
not only tests the *non-existence* of name in the current context, but also evaluates the value of name in the JSON model. If
name is not true (see below for what true means), the body is processed.
Note that the value of name is evaluated as follows:
"" or ' ' will evaluate to false, boolean false will evaluate to false as well, null, or undefined will evaluate to false.
Numeric 0 evaluates to true, so does, string "0", string "null", string "undefined" and string "false".
Empty array -> [] is evaluated to false and empty object -> {} and non-empty object are evaluated to true.
Here is an example of doing something special when the arrray is empty.
Template:
<ul>
{#friends}
<li>{name}, {age}{~n}</li>
{:else}
<p>You have no friends!</p>
{/friends}
</ul>
JSON:
11/21/2014 5:30 AM
8 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
{
friends: [
{ name: "Moe", age: 37 },
{ name: "Larry", age: 39 },
{ name: "Curly", age: 35 }
]
}
In the original dust, it does not trigger the {:else} block. Our version fixed it, to keep # and ? consistent
Take special care if you are trying to pass a boolean parameter. param=true and param=false do not pass true/false as you might
expect. They are treated as references to variables named "true" and "false". Unlike JavaScript, they are not reserved names.
Note that they are not reserved in JSON either so you can have a property named true or false. So you might think to pass 0 and
1 to your boolean-like parameter. That won't work either. dust's boolean testing ( {?xxx} is more of an existence test than a
boolean test. Therefore, with param=1 and param=0 both value exists and so are considered true. Your best bet is to pass 1 and
"", e.g. param=1 or param="". You could also leave off param="" if you are sure the name is not elsewhere in your JSON data and
accessible.
Partials
A Dust template named "xxx" is authored in a file named xxx.dust. You can have multiple .dust files and reference one Dust
template as part of another one. This is the basis for "components or reusable templates for tasks like a common header and
footer on multiple pages. Note that the .dust file extension is used here in examples but .tl is also commonly seen. Since it only
matters to the build process you can use whatever extension works for you.
Let's peek under the covers to see how the Dust template rendering knows about a template. As we said earlier, Dust templates
are compiled to JavaScript. Part of that compiled result is a call to dust.register(name,
functionThatImplementsCompiledTemplate). The register call associates a template name with the function to run that template.
So consider this example of how partials might be used:
{>header /}
... template for the body of the page...
{>footer /}
As long as the JavaScript for the header.dust and footer.dust templates is loaded and registered prior to executing this template, it
will run the header template, then its own body view and finally the footer template.
The partial reference syntax {>name /} also supports paths so you can have a template at a path like "shared/header.dust} and
reference it as {>"shared/header" /}. This allows partials to be organized into library-like structures using folders.
Like sections, partials accept parameters so you can build reusable components that are parameterizable easily. This gives you
the same foundation for building libraries as other languages. By passing all the data into the partial using parameters, you isolate
the partial from any dependence on the context when it is invoked. So you might have things like {>header mode="classic" /} to
control the header behavior.
Just like in sections, inline parameters will not override the current context if a property of the same name exists. For example, if
the current context already has {name: "Albert"} adding name as a parameter will not override the value when used inside the
partial foo.
11/21/2014 5:30 AM
9 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
For dust users of versions prior to 2.0.0, if you use parameters to pass an object like:
homeAddress: {
street: "1 Main St",
city: "Anytown"
}
{>displayAddress address=homeAddress /}
then you will not be able to reference {address.street} or {address.city} in the body of the partial. These get treated as a path
reference and the params are higher in the context stack at the point of reference so cannot be found. You need to code such
things as:
{#address}
{street} {city}
{/address}
From dust 2.0.0 on, you can write the more natural
{address.street} {address.city}
This sort of usage might suit a case where you have a multi-page flow and the controller could pass "page1", "page2",... in the
data model to dynamically choose which partial to use to implement the view.
Helpers
Helpers can be found in a separate repo at: https://github.com/linkedin/dustjs-helpers In order to use the dust helpers described
below, you need to load the dustjs-linkedin library first and then load the dustjs-helpers library. If you are using node.js, then the
following require statements will ensure you have the needed dust code available.
require('dustjs-linkedin');
require('dustjs-helpers');
Logic Helpers
{@select key="xxx"} + @eq, @ne, @lt, @lte, @gt, @gte, @default, Available in Dust 1.0
Select provides a key value that can be tested within its scope to output desired values. It mimics the switch/case statement. Here
are some examples:
{@select key=\"{foo}\"}
{@eq value=\"bar\"}foobar{/eq}
{@eq value=\"baz\"}foobaz{/eq}
{@default} - default Text{/default}
{/select}
{@select key=foo}
{@gte value=5}foobar{/gte}
{/select}
Each test condition is executed and if true, the body is output and all subsequent condtions are skipped. If no test condition has
11/21/2014 5:30 AM
10 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
been met and a @default is encountered, it will be executed and the select process terminates.
The @eq (for example) can be used without a {@select}.The most common pattern of usage would be for an HTML
<select>/<option> list to mark the selected element with a "selected" attribute. The code for that looks like this where {#options} is
an array of options from the data model. Here the key is directly on the eq rather than on the select helper.
<select name="courses">
{#options}
<option value="{value}"{@eq key=value value=courseName}
{/options}
</select>
selected="true"{/eq} >{label}</option>
Similarly, {@ne}, {@lt}, {@gt}, {@lte}, {@gte} can be used standalone and allow nesting. The following is a valid example
{@eq key="CS201" value=courseName}
{@eq key="CS101" value=prereq}
print it is CS201 course and has CS 101 as prereq
{/eq}
{/eq}
Note that all of {@eq}, {@ne}, {@lt}, {@gt}, {@lte}, {@gte} support an else block so you can output an alternative result if the test
is false.
{@eq key="CS201" value=courseName}
You are enrolled in CS201
{:else}
You are not enrolled in CS201
{/eq}
The helper computes a result using the key, method, and operand values. Some examples will clarify:
{@math
{@math
{@math
{@math
{@math
The above evaluates the mod with the given key and operand i.e $idx % 2 and then checks if the output is 0, and prints the block
inside the @eq helper, if not the else block. Be careful to use numeric values for tests and not strings, e.g. {eq value="0"} will
never be true.
11/21/2014 5:30 AM
11 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Another example
{@math key="13" method="add" operand="12"}
{@gt value=123}
13 + 12 > 123
{/gt}
{@default}
Math is fun
{/default}
{/math}
Using the nested @eq @lt etc. syntax allows you to output values like a select/case similar to the select helper.
Caveat #1: In the above example, if there is a possibility of undefined or false value for the {x} or {y} in the JSON, the correct
syntax would be to check it exists and then check for {x} > {y}. This is a known limitation since, {x} returns nothing when the value
of x is undefined or false and thus results in invalid js condition in the if helper
{@if cond="'{x}'.length && '{y}.length && {x} < {y} && {b} == {c} && '{e}'.length > 0 || '{f}'.length > 0 "}
<div> x is less than y and b == c and either e or f exists in the output </div>
{/if}
Caveat #2: The if helper internally uses javascript eval, for complex expression evaluation. Excessive usage of if may lead to
sub-optimal performance with rendering, since eval is known to be slow.
Other Helpers
Dust provides a mechanism to extend the capabilities of the templating solution. Currently there is a small set of helpers that
come with the release:
As written this will produce Hurley,Kate,Sawyer,Desmond, leading to the "dangling comma problem". This can be fixed by using
the {@sep} helper tag as follows:
My friends are:
{#friends}
{name}{@sep},{/sep}
{/friends}
11/21/2014 5:30 AM
12 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
The {@sep} helper tag will output it's body content unless this is the final iteration of the containing loop.
Here we are using idx to generate a unique id for each option tag in a dropdown. Therefore, we would have "id_0, id_1,... for id
values. Within the idx helper {.} references the current iteration count.
Now that we have defined a base template with named blocks pageHeader, bodyContent, pageFooter , let's look at how a child
template can use it to supply body content and override the pageFooter. First, you insert the base template as a partial. Then you
use one or more "inline partials" defining the values for the named blocks in the template.
11/21/2014 5:30 AM
13 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Child template
{! First, insert the base template as a partial !}
{>"shared/base_template"/}
{! Then populate the base template named blocks. Supply the desired bodyContent and pageFooter !}
{<bodyContent}
<p>These are your current settings:</p>
<ul>
<li>xxxx</li>
<li>yyy</li>
</ul>
{/bodyContent}
{<pageFooter}
<hr>
<a href="/contactUs">About Us</a> |
<a href="/contactUs">Contact Us</a>
{/pageFooter}
Note that inline partials like {<name}xxx{/name}, define "name" globally within the template. While this might be useful, remember
the pains caused by global variables in JavaScript and use these with the knowledge that others can stomp on your chosen name
inadvertently.
If you include the "compiled" string as part of a script block of JS that you load, then the "intro" template will be defined and
registered. If you want to do it immediately then do:
dust.loadSource(compiled);
11/21/2014 5:30 AM
14 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Warning: pre-2.5.0, this will break Dust when you compile very large (over 150KB or so) templates.
11/21/2014 5:30 AM
15 of 15
https://github.com/linkedin/dustjs/wiki/Dust-Tutorial#wiki-Dust_Tutorial
Parameters can come in many forms,e.g. param=2, param={a}, param="{a}". Some can be accessed and used directly from the
params parameter to the helper. Others require evaluating a function to obtain the final value. You can avoid all the bother around
this by using dust.helpers.tap(params.name, chunk, ctx) which returns you the final value of the parameter.
If you need to work with the body of the helper, then the following will get it for you.
var body = bodies.block;
To evaluate the body, you call it with body(chunk, context). There are other parameters if you intend to emulate a looping structure
like a section letting you define $idx and $len.
context is the dust context stack. Normally you will just use the dust get method when retrieving values from the context stack. If
you need a deeper knowledge, take a look at the code for Context.prototype.get. Prior to dust 2.1.0, getPath was used for
references to paths. With dust 2.1.0, that capability has been incorporated into get.
Generally, you should always return the output of your helper as a chunk using the write method on it to add your helper's
generated output to the accumulation in the chunk. Dust chains the result of your helper to further actions which expect to be able
to add to the chunk.
11/21/2014 5:30 AM