System overview Using the editor The Exotool language HTML templates The diagram The element catalogue
Exotool HTML Templates — Reference Manual
This is a reference for Exotool HTML templates: the {{ ... }} templating
syntax used to generate HTML (and, where useful, Exotool source) from data and
parameters. It is a companion to the language reference (exotool_docs("language")),
which documents the core Exotool language. For the big picture of the Exotool
platform, see the System Overview (exotool_docs("overview")); for building apps in
the visual editor, see Using the Exotool Editor (exotool_docs("editor")).
Relationship to the core language. HTML templates are a separate syntax from the core Exotool language. A template's output can itself be Exotool source code, which is then compiled normally — so the two languages compose.
Contents
- Overview
- How templates are processed
- Literal text
- Value references
- Variable paths and contexts
- Special variables
- Constants
- Function calls (helpers)
- Block helpers
- Parameter references
- Asset and element references
- Parameter declarations
- Worked example
- Quick reference
1. Overview
Exotool HTML templates are a Handlebars / Template7-compatible templating language. (The bundled UI framework is Framework7, whose template engine is Template7; Exotool follows that syntax, so if you know Handlebars or Template7 you already know most of this.)
A template is plain text interspersed with {{ ... }} expressions:
<h1>{{heading}}</h1>
<ul>
{{#each items}}
<li>{{name}} — {{price}}</li>
{{/each}}
</ul>
On top of the standard Template7 syntax, Exotool adds a few extensions:
- Parameter references —
{{parameters.foo}}— pull configurable values from an element's parameter set. - Asset / element references —
{{@file ...}},{{@image ...}},{{@element ...}}— reference uploaded files, images, and other elements. - Parameter declarations —
{{#Declare-Parameter ...}}— declare a configurable parameter (and the editor UI control for it) inline.
2. How templates are processed
A template passes through up to three stages:
- Editor scanning (design time). The editor scans template text for
{{ ... }}expressions to build the element's parameter specs — the property-panel controls a user edits. References like{{parameters.x}},{{@file x}},{{@image x}}, and explicit{{#Declare-Parameter ...}}statements drive this. An undeclared{{parameters.x}}auto-generates a text-input parameter;{{@file x}}/{{@image x}}auto-generate file/image asset controls. - Static expansion (compile time). The compiler expands everything that is static with respect to run time — resolving fixed parameters, assets, and constant branches — and leaves genuinely dynamic parts as Template7 markup.
- Client-side rendering (run time). The Template7 runtime renders the remaining dynamic markup against the live data context.
{{#Declare-Parameter}}is "passthru". When the template is rendered, a{{#Declare-Parameter ...}}statement is ignored — it produces no output. It exists purely to be read by the editor's parameter system. See §12.
3. Literal text
Any text outside {{ ... }} is emitted verbatim — a run of characters up to the
next {{. Templates need not be well-formed HTML; the engine treats them as
text.
Hello, world! <!-- emitted as-is -->
4. Value references
The basic interpolation is a reference: a variable path (or function call, or special variable) wrapped in double braces.
{{ <reference> }}
{{name}}
{{user.email}}
{{count items}}
A reference resolves the value against the current data context and inserts
it into the output. Optional whitespace is allowed inside the braces:
{{ name }} is equivalent to {{name}}.
5. Variable paths and contexts
A variable reference can be, tried in order:
- a function call
- a parent-context reference (
../) - a dotted path (
a.b.c) - a special variable
- a plain variable
Plain variables and dotted paths
{{name}}
{{user.address.city}}
A variable name is any identifier (exotool_docs("language")) except
the reserved word else. A dotted path (a.b.c) walks into nested
properties of the current context.
Parent context (../)
Inside a block, ../ steps out to the enclosing data context — the same
meaning as in Handlebars/Template7:
{{#each items}}
{{name}} belongs to {{../groupName}}
{{/each}}
../../x steps out two levels, and so on.
6. Special variables
Within blocks (especially {{#each}}), these special variables are available:
| Variable | Meaning |
|---|---|
@index |
the current index in an iteration |
@key |
the current key (when iterating an object/associative array) |
@global |
the global/root data context (@global.x.y walks into it) |
@elementvar |
the element's own variable space (@elementvar.x is the variable x of this element, kept under the element's key) |
@errorvar |
the element's error variable (<key>_error, set by forms and prompts) |
this |
the current data context itself; this.name reaches a data field even when name is one of the element's own names (below) |
{{#each rows}}
Row {{@index}}: {{value}}
{{/each}}
7. Constants
Where an expression is expected (block conditions, function arguments), literal constants may be used — a number or a string:
{{#ifeq status "active"}} ... {{/ifeq}}
{{#ifeq count 0}} ... {{/ifeq}}
Numbers and strings follow the same lexical rules as the
core language (exotool_docs("language")) (single- or
double-quoted strings).
8. Function calls (helpers)
A reference may call a built-in helper function:
<name>(<arg>, <arg>, ...)
where each argument is a template expression (a constant or variable reference).
Helpers may also be written Handlebars-style with space-separated arguments, as
in {{count items}}.
The recognized built-in helper names are:
| Helper | Purpose |
|---|---|
values |
the values of a collection |
count |
number of items |
sum |
sum of values |
max |
maximum value |
min |
minimum value |
any |
whether any item is present/truthy |
dateString |
format a value as a date |
timeString |
format a value as a time |
dateTimeString |
format a value as date + time |
not |
logical negation |
intern |
intern/normalize a value |
<p>{{count items}} items, total {{sum prices}}</p>
<p>Updated {{dateString lastModified}}</p>
{{#if not(done)}} still working… {{/if}}
9. Block helpers
Block helpers wrap a body of template content between an opening and closing tag. The body is itself a sequence of any template constructs, so blocks nest freely.
{{#each}} — iteration
{{#each <reference>}} <body> {{/each}}
Iterates over a collection, rendering the body once per item with the item as
the current context. Use @index / @key for position, and ../ to reach the
enclosing context.
{{#each products}}
<li>{{@index}}. {{name}}</li>
{{/each}}
{{#with}} — context shift
{{#with <reference>}} <body> {{/with}}
Renders the body once with <reference> as the current context — handy for
drilling into a nested object without repeating its path.
{{#with user.address}}
{{street}}, {{city}}
{{/with}}
{{#if}} / {{else}} — conditional
{{#if <exp>}} <body> {{/if}}
{{#if <exp>}} <body> {{else}} <body> {{/if}}
Renders the body when <exp> is truthy; the optional {{else}} branch renders
otherwise.
{{#if isLoggedIn}}
Welcome back, {{name}}.
{{else}}
Please sign in.
{{/if}}
{{#ifeq}} / {{else}} — equality conditional
{{#ifeq <exp> <exp>}} <body> {{/ifeq}}
{{#ifeq <exp> <exp>}} <body> {{else}} <body> {{/ifeq}}
Renders the body when the two expressions are equal.
{{#ifeq role "admin"}}
<button>Manage users</button>
{{else}}
<span>Standard account</span>
{{/ifeq}}
An
{{else}}clause binds when present.
10. Parameter references
(Exotool extension.) A parameter reference pulls a value from the element's configurable parameter set — the values a user edits in the property panel.
(parameters | @parameters).<name>[.<name> ...]
{{parameters.cycle_time}}
{{parameters.do_on_cycle}}
{{@parameters.title}}
Both parameters. and @parameters. prefixes are accepted.
If a referenced parameter has not been explicitly declared, the editor
auto-generates a parameter spec for it (a plain text-input control), so the
parameter shows up in the property panel automatically. To control the type,
title, default, etc., declare it explicitly with
{{#Declare-Parameter}}.
A template producing Exotool code can interleave parameter references with core language constructs. For example:
{{#Declare-Parameter name:"cycle_time", title:"Cycle Time (Msec)", default_value:5000 }}
{{#Declare-Parameter name:"do_on_cycle", type:"textarea", title:"Do On Cycle", default_value:"" }}
declare_location {{identifier}} client
{{identifier}} = {{parameters.cycle_time}}
{{parameters.do_on_cycle}}
JS< setTimeout(function() { Elements.{{identifier}}.launch(); }, Math.max(100, Env.{{identifier}})) ; >JS
Here the template emits Exotool source — a declare_location, an assignment, an
inlined parameter body, and a JS< ... >JS block — which is then compiled by
the core language toolchain.
11. Asset and element references
(Exotool extensions.) These reference project resources by name.
{{ @file <name> }}
{{ @image <name> }}
{{ @element <name> }}
| Reference | Resolves to | Editor side effect |
|---|---|---|
{{@file name}} |
a referenced file asset (e.g. its URL/path) | auto-generates a file_asset parameter control (unless name is already declared) |
{{@image name}} |
a referenced image asset | auto-generates an image_asset parameter control (unless name is already declared) |
{{@element name}} |
another element by name | — |
<img src="{{@image logo}}">
<a href="{{@file brochure}}">Download</a>
<div>{{@element header}}</div>
12. Parameter declarations
(Exotool extension.) {{#Declare-Parameter ...}} explicitly declares a
configurable parameter — its type, title, default value, and UI behavior.
{{#Declare-Parameter <fields> }}
The body is a comma-separated set of key: value pairs (quotes on keys are
optional, and trailing commas are tolerated). A name field is required.
{{#Declare-Parameter
name: "notice",
title: "Title of Notice",
type: "text",
default_value: ""
}}
Common fields:
| Field | Meaning |
|---|---|
name (required) |
the parameter's name, as used in {{parameters.<name>}} |
title |
label shown in the property panel |
type |
control type — e.g. text, textarea, code, template, array, associative_array, file_asset, image_asset, popup_text |
default_value |
initial value |
expert_level |
how advanced the control is (visibility tiering) |
For collection types (array, associative_array) the editor adds
create/delete behavior, configurable via allow_create / allow_delete and
default_item_value.
Rendering behavior.
{{#Declare-Parameter ...}}emits nothing in the rendered output — it is treated as a passthru/no-op. It exists only to inform the editor's parameter system. The value the parameter holds is what you interpolate elsewhere via{{parameters.<name>}}.
13. Worked example
This section follows a single template all the way through the three processing stages: what the editor extracts at design time, what the compiler expands at compile time, and what the client renders at run time.
The template
A "featured products" panel. It has a configurable heading and an empty-state message (both parameters), a project logo (an image asset), and a list rendered from live data:
{{#Declare-Parameter
name: "heading",
title: "Panel heading",
type: "text",
default_value: "Featured products"
}}
{{#Declare-Parameter
name: "empty_message",
title: "Shown when the list is empty",
type: "textarea",
default_value: "Nothing to show yet."
}}
<section class="featured">
<header>
<img class="logo" src="{{@image brand_logo}}">
<h2>{{parameters.heading}}</h2>
</header>
{{#if any(products)}}
<ul>
{{#each products}}
<li class="item">
<span class="num">{{@index}}.</span>
{{name}} — {{dateString addedOn}}
{{#ifeq status "sale"}}
<em class="badge">On sale!</em>
{{else}}
<span class="from">from {{../storeName}}</span>
{{/ifeq}}
</li>
{{/each}}
</ul>
<footer>{{count products}} items · total {{sum products.price}}</footer>
{{else}}
<p class="empty">{{parameters.empty_message}}</p>
{{/if}}
</section>
Note the variety of constructs in play: two
parameter declarations, an
image asset reference, two
parameter references, an
any guard, an
{{#each}} loop using
@index and a ../ parent reference,
an {{#ifeq}}/{{else}} branch, and count/sum helpers.
Stage 1 — editor scanning (design time)
The editor scans the {{ ... }} expressions and builds the element's parameter
specs. From this template it produces:
| Source | Parameter spec produced |
|---|---|
{{#Declare-Parameter name:"heading" …}} |
explicit text parameter heading, default "Featured products" |
{{#Declare-Parameter name:"empty_message" …}} |
explicit textarea parameter empty_message |
{{@image brand_logo}} |
auto-generated image_asset control brand_logo |
{{parameters.heading}} |
already declared → no new control |
{{parameters.empty_message}} |
already declared → no new control |
The user now sees three controls in the property panel — a heading text box, an
empty-message text area, and an image picker for brand_logo — and fills them
in. Suppose they set:
heading="This week's picks"empty_message="Check back soon!"brand_logo→ an uploaded image that resolves to/assets/img/logo_v3.png
Stage 2 — static expansion (compile time)
The compiler resolves everything static with respect to run time. The
configured parameters and the asset reference are fixed, so they are
substituted; the {{#Declare-Parameter}} statements are passthru and drop out.
The products data is dynamic (it comes from the live context), so all of the
{{#if}} / {{#each}} / {{#ifeq}} markup is left intact for the client:
<section class="featured">
<header>
<img class="logo" src="/assets/img/logo_v3.png">
<h2>This week's picks</h2>
</header>
{{#if any(products)}}
<ul>
{{#each products}}
<li class="item">
<span class="num">{{@index}}.</span>
{{name}} — {{dateString addedOn}}
{{#ifeq status "sale"}}
<em class="badge">On sale!</em>
{{else}}
<span class="from">from {{../storeName}}</span>
{{/ifeq}}
</li>
{{/each}}
</ul>
<footer>{{count products}} items · total {{sum products.price}}</footer>
{{else}}
<p class="empty">Check back soon!</p>
{{/if}}
</section>
Stage 3 — client-side rendering (run time)
At run time the Template7 runtime renders the remaining markup against the live data context. Suppose the context is:
{
storeName: "Acme",
products: [
{ name: "Widget", status: "sale", addedOn: 1718409600, price: 9.99 },
{ name: "Gadget", status: "new", addedOn: 1718496000, price: 19.50 }
]
}
any(products) is truthy, so the {{#each}} branch renders. For each product,
{{@index}} gives its position, {{../storeName}} reaches out of the loop to
"Acme", and {{#ifeq status "sale"}} picks the badge or the "from" line. The
result:
<section class="featured">
<header>
<img class="logo" src="/assets/img/logo_v3.png">
<h2>This week's picks</h2>
</header>
<ul>
<li class="item">
<span class="num">0.</span>
Widget — Jun 15, 2024
<em class="badge">On sale!</em>
</li>
<li class="item">
<span class="num">1.</span>
Gadget — Jun 16, 2024
<span class="from">from Acme</span>
</li>
</ul>
<footer>2 items · total 29.49</footer>
</section>
Had products been empty, any(products) would be false and the {{else}}
branch would render the configured empty message instead:
<section class="featured">
<header>
<img class="logo" src="/assets/img/logo_v3.png">
<h2>This week's picks</h2>
</header>
<p class="empty">Check back soon!</p>
</section>
Why the split matters. The heading, empty message, and logo were baked in at compile time (one less thing for the client to compute), while the data-driven list stayed dynamic. The exact static/dynamic boundary depends on what the compiler can prove constant; treat the stage-2 output above as illustrative of the kind of expansion performed, not a byte-exact guarantee. The
dateStringformatting shown is likewise indicative of the helper's intent.
14. Quick reference
Construct summary
| Construct | Form |
|---|---|
| literal text | anything outside {{ }} |
| value reference | {{ reference }} |
| dotted path | {{ a.b.c }} |
| parent context | {{ ../x }} |
| special vars | {{@index}} {{@key}} {{@global}} |
| function call | {{ name(arg, arg) }} |
| each | {{#each reference}} … {{/each}} |
| with | {{#with reference}} … {{/with}} |
| if / else | {{#if exp}} … {{else}} … {{/if}} |
| ifeq / else | {{#ifeq exp exp}} … {{else}} … {{/ifeq}} |
| parameter ref | {{parameters.name}} / {{@parameters.name}} |
| file asset | {{@file name}} |
| image asset | {{@image name}} |
| element ref | {{@element name}} |
| declare parameter | {{#Declare-Parameter …fields… }} |
Reserved/keyword tokens
{{#each {{/each}}
{{#with {{/with}}
{{#if {{else}} {{/if}}
{{#ifeq {{/ifeq}}
{{#Declare-Parameter
@file @image @element
@key @index @global @elementvar @errorvar
parameters @parameters
this _element_path
else (reserved — not a valid plain variable name)
Names that are the element's own. A bare name is looked up first among the element's own parameters and these special names, and only a name found in neither is read from the data:
title identifier classes attributes thisname permID thisobject thispage
parentobject referrer transition appTitle ui_style subs subelements links
template expansion Elements
So {{title}} is always the element's own title, never a data field called title,
and the same holds for any parameter the element has (color in a swipeout, for
instance). Name data fields so they do not collide (cat_name, st_name), or reach a
colliding field through the data explicitly: {{this.title}}.
Built-in helper functions:
values count sum max min any
dateString timeString dateTimeString
not intern