Professional Documents
Culture Documents
■ What is a template?
Sometimes we may want to dynamically generate the JSON from the client side instead of
directly using a JSON returned from the server.
It may be for rendering a local user input, it may be for rendering a result from a 3rd party API.
There are several cases where templating makes sense. Learn more here
valid expressions
Jasonette template engine takes advantage of the native javascript engine. This means:
{
"items": [
{
"type": "image",
"url": "{{$jason.image}}"
},
{
"type": "label",
"text": "{{$jason.username}}"
}
]
}
Jasonette implements the native javascript engine to evaluate expressions. So you can use any
javascript expression inside {{ }} .
{
"items": [
{
"type": "label",
"url": "Full JSON string"
},
{
"type": "label",
"text": "{{JSON.stringify($jason)}}"
}
]
}
If it involves multiple instructions, you can even write a full fledged function inside the
template expression.
{
"items": [
{
"type": "label",
"url": "Reversed Fullname"
},
{
"type": "label",
"text": "{{var sorted_posts = $jason.posts.sort(function(a,b){ return new Date(b.created_at
}
]
}
example
Here's an example where we use the $geo.get action to get the current location, and render it
dynamically using the template.
{
"$jason": {
"head": {
"title": "Display location",
"actions": {
"type": "$geo.get",
"success": {
"type": "$render"
}
},
"templates": {
"body": {
"sections": [{
"items": [
{
"type": "label",
"text": "Latitude: {{ $jason.coord.split(',')[0] }}"
},
{
"type": "label",
"text": "Longitude: {{ $jason.coord.split(',')[1] }}"
}
]
}]
}
}
}
}
}
In above JSON markup, the $jason.body part is missing, because we will dynamically generate
it via $render .
The $render action will render the data with the template, and insert it into where
$jason.body should be. The result would be:
{
"$jason": {
"head": {
"title": "Display location",
"actions": {
"type": "$geo.get",
"success": {
"type": "$render"
}
},
"templates": {
"body": {
"sections": [{
"items": [
{
"type": "label",
"text": "Latitude: {{ $jason.coord.split(',')[0] }}"
},
{
"type": "label",
"text": "Longitude: {{ $jason.coord.split(',')[1] }}"
}
]
}]
}
}
},
"body": {
"sections": [{
"items": [
{
"type": "label",
"text": "Latitude: 12.1234"
},
{
"type": "label",
"text": "Longitude: 23.2345"
}
]
}]
}
}
}
■ Syntax
JSON
Let's take a look at how JSON templating works:
1. Loop (#each)
To demonstrate looping, let's look at an example. We have a static JSON that looks like this:
{
"body": {
"sections": [{
"items": [
{
"type": "label",
"text": "Homer"
},
{
"type": "label",
"text": "Marge"
},
{
"type": "label",
"text": "Lisa"
},
{
"type": "label",
"text": "Bart"
},
{
"type": "label",
"text": "Maggie"
}
]
}]
}
}
IF you look at each item, the only part that's custom is the name ("Homer", "Marge", "Lisa",
"Bart", "Maggie"). We want to shorten this so that we don't have to rewrite "type": "label" for
every item.
{
"$jason": {
"head": {
"data": {
"members": [
{ "name": "Homer" },
{ "name": "Marge" },
{ "name": "Lisa" },
{ "name": "Bart" },
{ "name": "Maggie" }
]
}
}
}
}
Then we will declare a body template that will iterate through this members array and turn
each into renderable item.
{
"$jason": {
"head": {
"data": {
"members": [
{ "name": "Homer" },
{ "name": "Marge" },
{ "name": "Lisa" },
{ "name": "Bart" },
{ "name": "Maggie" }
]
},
"templates": {
"body": {
"sections": [{
"items": {
"{{#each members}}": {
"type": "label",
"text": "{{name}}"
}
}
}]
}
}
}
}
}
The #each keyword will iterate through the expression that comes after it ( members ) and
generate a JSON array from the result, ending up with the final JSON markup we saw at the
beginning.
When there's only a single #each expression it's simple. But when we have multiple #each
Here's an example:
{
"{{#each families}}": {
"{{#each members}}": {
"type": "label",
"text": "{{name}}"
}
}
}
What if we want to access families object from inside the nested {{#each members}} loop?
{
"{{#each families}}": {
"{{#each members}}": {
"type": "label",
"text": "{{families.length}}"
}
}
}
because inside the {{#each members}} loop, the context is each family member. Above
expression will result in the parser trying to access for example members[0].families.length
instead of families.length . It will throw an error because a member object doesn't contain a
families attribute.
We need a way to access the root context. This is where $root comes in.
Whenever you're inside a loop, you can refer to the root context using $root . So above
example will be:
{
"{{#each families}}": {
"{{#each members}}": {
"type": "label",
"text": "{{$root.families.length}}"
}
}
}
You can use the $root object to access everything at the root level, such as $get (through
$root.$get ), $cache (through $root.$cache ), $global (through $root.$global ), etc.
2. Conditional (#if/#elseif/#else)
Conditionals are used to conditionally render their children only when the expression
evaluates to true .
syntax
[
{
"{{#if (EXPRESSION A)}}": (JSON)
},
{
"{{#elseif (EXPRESSION B)}}": (JSON)
}
{
"{{#else (EXPRESSION C)}}": (JSON)
}
]
The template will walk through the items in the array sequentially until it encounters an
conditional expression that's true. Then it will only render its child JSON.
Note
Example
Let's say we are are trying to render the following return value ( $jason ):
{
"data": {
"name": "Homer"
}
}
What happens when we run above data through the following template?
{
"type": "label",
"text": [
{
"{{#if $jason.data.name=='Bart'}}": "Ay Caramba!"
},
{
"{{#elseif $jason.data.name=='Homer'}}": "Donuts..."
}
]
}
{
"type": "label",
"text": "Donuts..."
}
3. "this"
this is a javascript keyword used to refer to the current context. Let's look at what that
means:
{
"members": [{"name": "Homer"}, {"name": "Marge"}, {"name": "Lisa"}, {"name": "Bart"}, {"name":
}
{
"{{#each members}}": {
"type": "label",
"text": "{{name}}"
}
}
{
"members": ["Homer", "Marge", "Lisa", "Bart", "Maggie"]
}
Now we're lost. Since each individual element in the members array is just a string instead of an
object, we need some way to refer to the object itself.
This is where this comes in. To handle this situation we can write the following template:
{
"{{#each members}}": {
"type": "label",
"text": "{{this}}"
}
}
Keep in mind that the change in context makes global objects such as $get , and $cache
inaccessible. You can use the $root object to get at them, e.g. $root.$get .
4. Advanced
Since inception, the template engine has added a lot of more useful features, such as
$index for keeping track of the current item's index within a loop
#let API to define local variables
#concat for merging two arrays
#merge for merging two objects
#?: Existential operator for including or excluding a key/value pair altogether based on the
parsed result
The template itself has become too much of a sophisticated beast that it's been spun out to a
separate project.
Non-JSON
Let's take a look at how non-JSON (CSV, RSS, HTML) templating works:
CSV
When you have a raw CSV content, you can parse it into JSON format before feeding it into a
template.
RSS
When you have an RSS content, you can parse it into JSON format before feeding it into a
template.
Unlike other formats like CSV and RSS, Jasonette implements a separate HTML template
engine, so we don't need to parse HTML into JSON.
Instead, we convert HTML DOM elements into JSON, using the built-in HTML to JSON
parser, which is built on top of Cheerio library, which has similar syntax to jQuery
How to use
It starts with an HTML content. You can fetch HTML content by making $network.request calls
with data_type of html , like this:
{
"$jason": {
"head": {
"actions": {
"type": "$network.request",
"options": {
"url": "http://www.techmeme.com/river",
"data_type": "html"
}
}
}
}
}
In order to render it using the html parser, you need to call $render with data_type of html :
{
"$jason": {
"head": {
"actions": {
"type": "$network.request",
"options": {
"url": "http://www.techmeme.com/river",
"data_type": "html"
},
"success": {
"type": "$render",
"options": {
"type": "html"
}
}
}
}
}
}
The HTML template engine automatically sets the <body> element as $jason .
From there we can use the jQuery syntax to parse and render content:
{
"$jason": {
"head": {
"actions": {
"type": "$network.request",
"options": {
"url": "http://www.techmeme.com/river",
"data_type": "html"
},
"success": {
"type": "$render",
"options": {
"type": "html"
}
}
},
"templates": {
"body": {
"sections": [
{
"items": {
"{{#each $jason.find('tr.ritem')}}": {
"type": "vertical",
"components": [
{
"type": "label",
"text": "{{$(this).find('td > a').text()}}"
},
{
"type": "label",
"text": "{{$(this).find('cite').text()}}"
},
{
"type": "label",
"text": "{{$(this).find('td').first().text() + ' ' + $(this).closest('tabl
}
],
"href": {
"view": "web",
"url": "{{$(this).find('td > a').attr('href')}}"
}
}
}
}
]
}
}
}
}
}
Make a separate network request for data, then render the response For example here's a
JSON markup that renders a list of labels:
{
"$jason": {
"head": {
...
},
"body": {
"sections": [{
"items": [{
"type": "label",
"text": "This is row 1"
}, {
"type": "label",
"text": "This is row 2"
}, {
"type": "label",
"text": "This is row 3"
}, {
"type": "label",
"text": "This is row 4"
}, {
"type": "label",
"text": "This is row 5"
}, {
"type": "label",
"text": "This is row 6"
}, {
"type": "label",
"text": "This is row 7"
}, {
"type": "label",
"text": "This is row 8"
}]
}]
}
}
}
As you can see, the "type": "label" part is repeated for each item.
1. Notice there's no body under $jason here ( $jason.body ). It's because we're going to
dynamically generate the body using the body template inside templates
( $jason.head.templates.body ).
2. Also notice the actions attribute contains a $load attribute, so this will be triggered as
soon as the view loads.
1. The Jason app loads the JSON shown above, from our server.
2. There's no body attribute so nothing is rendered on the screen by default. However, notice
there's a body attribute under templates . This is the template that will be rendered as the
body later.
3. Immediately after the view loads, the $load action gets automatically triggered by the
system.
4. $load makes a $network.request call with the url specified. We will need to return the
following data from the API:
You can render templates using any type of data, which includes local variables you can set
using form components such as:
textfield
textarea
search
etc.
Example: Below, we render the label using a local variable named message , which is
automatically set whenever the textfield value changes. Note that there is no top level body
element after head . Instead we have a body template, which will be rendered into body
whenever we call the $render action.
{
"$jason": {
"head": {
...
"actions": {
"$load": {
"type": "$render"
},
"$pull": {
"type": "$render"
}
},
"templates": {
"body": {
...
"items": [
{
"type": "textfield",
"name": "message"
},
{
"type": "label"
"text": "{{$get.message}}"
}
]
...
}
}
}
}
}
3. Device API generated data
geolocation
addressbook
camera
timer
etc.
Example: Below, we access the geolocation device sensor and render its result. Since our
server has no knowledge of the device sensor data, templates are the only way to go in this
case.
{
"$jason": {
"head": {
...
"actions": {
"$load": {
"type": "$geo.get",
"success": {
"type": "$render"
}
}
},
"templates": {
"body": {
...
"items": [
{
"type": "label"
"text": {{$jason.coord}}"
}
]
...
}
}
}
}
}
4. Reduce redundancy
Sometimes you simply want to separate view from model to avoid lots of code redundancy. See
the below data section for details.
1. Inline data
The head.data attribute is used to automatically fill in the body template if one exists.
When a view loads,
Here's a Jason markup without a template/data. As you can see, the label items mostly repeat,
except for the text attribute.
{
...
"body": {
"sections": [
"items": [
{
"type": "label",
"text": "Ethan",
"style": {
"color": "#000000",
"size": "14",
"font": "HelveticaNeue-Bold",
"padding": "10",
"background": "rgba(0,0,0,0.5)",
"width": "300",
"height": "100"
}
}
...
{
"type": "label",
"text": "John",
"style": {
"color": "#000000",
"size": "14",
"font": "HelveticaNeue-Bold",
"padding": "10",
"background": "rgba(0,0,0,0.5)",
"width": "300",
"height": "100"
}
}
{
"type": "label",
"text": "Samantha",
"style": {
"color": "#000000",
"size": "14",
"font": "HelveticaNeue-Bold",
"padding": "10",
"background": "rgba(0,0,0,0.5)",
"width": "300",
"height": "100"
}
}
]
]
}
}
This is essential, since your server has no knowledge of what it should render if the data to
render is a result of user interaction.
action.