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

  1. Overview
  2. How templates are processed
  3. Literal text
  4. Value references
  5. Variable paths and contexts
  6. Special variables
  7. Constants
  8. Function calls (helpers)
  9. Block helpers
  10. Parameter references
  11. Asset and element references
  12. Parameter declarations
  13. Worked example
  14. 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:

  1. 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.
  2. 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.
  3. 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:

  1. a function call
  2. a parent-context reference (../)
  3. a dotted path (a.b.c)
  4. a special variable
  5. 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 dateString formatting 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