Mustache Views
Learn how to use Mustache to create HTML views.
Overview
Stacklane uses Mustache, combined with additional extensions (pragmas), to create dynamic HTML views.
To use Mustache to generate HTML views, continue to use the ".html" extension, and on the first line of the HTML file use the special comment:
<!--TEMPLATE mustache-->
All files that use Mustache for the templating language, including partials/imports, must include on the first line.
Mustache views may be populated with data or other scripted variables by importing variables previously exported by Suppliers.
Routing
Mustache endpoints are routed according to their file name.
For example, /hello/world.html
responds to requests for GET /hello/world
.
index.html
may be used in the case where
the routing should be directory based. /hello/index.html
responses to requests for GET /hello/
.
Note: It will also respond to requests for GET /hello
(no trailing slash),
and redirect to the actual endpoint of /hello/
(trailing slash).
Link Absolute Paths
When referencing files or endpoints within your app
we highly recommend using "href" and "src" with
absolute values instead of relative values β
href="/here/there"
instead of
href="here/there"
.
This creates consistency in a number of other locations including script redirects, Mustache partial imports, and in general leaves no ambiguity.
Partials
Mustache partials may be thought of as file includes.
The syntax for including a partial is {{> nameOfPartial}}
.
By default there are no partials available. Stacklane uses pragmas to instruct what partials are available, and how they are named.
Directory of Partials
Name the directory with a leading underscore to indicate it is private:
/_partials/
. To allow the individual Mustache files in the directory
to be used as partials, use the pragma {{% partial /_partials/ }}
(note the trailing slash). Given a file in this directory /_partials/something.html
,
it may then be included via the standard syntax {{> something}}
.
A directory of partials may optionally be given a prefix. Using the above example,
{{% partial /_partials/ as my}}
brings in those partials with the "my-" prefix (hyphen is implied).
The same partial is now included using
{{> my-something}}
Single Files
A single partial may be imported by referencing its full path name without the
".html" extension. {{% partial /_partials/something }}
is accessible via {{> something}}
.
Single file partials also support aliases.
{{% partial /_something as somethingElse }}
is accessible via {{> somethingElse}}
.
SVG
SVG files may be included/inlined using the same nomenclature as Mustache partials. The only difference is the initial pragma.
Given a directory of SVG icons, the pragma
{{% svg /_icons/ as ic }}
would allow
importing of gear.svg using {{> ic-gear}}
.
Layout Templates
A common extension to Mustache is the ability to define a "master template".
This file contains the <html>
, <head>
,
and <body>
tags. Within the file there is at least one
placeholder which denotes where to "insert" a section of HTML from another Mustache file
(the file which uses this master layout template).
Master Template
/_layout.html
<!--TEMPLATE mustache-->
<!DOCTYPE html>
<html>
<head>
<title>{{$title}}Title{{/title}}</title>
...
</head>
<body>
...
{{$content}}Content{{/content}}
...
</body>
</html>
Template Using Layout
The file using the master layout template will include it similar to a partial.
Assuming a layout file named /_layout.html
, another file
would use this template like:
/index.html
<!--TEMPLATE mustache-->
{{% partial /_layout}}
{{<layout}}
{{$title}}My Title{{/title}}
{{$content}}
.. Custom Content Here ..
{{/content}}
{{/layout}}
Structured Data
In certain cases it helps to keep structured data outside of your Mustache file, and then include this for processing as any other variable. A similar and more robust approach is to use static model definitions.
YAML
Define a YAML file and then refer to it with the pragma: {{% yaml /_myData as myData }}
JSON
Define a JSON file and then refer to it with the pragma: {{% json /_myData as myData }}
HTML and SVG
Unescaped HTML or SVG may be emitted with triple brackets.
{{{ myModel.icon }}}
Keep in mind that this is only allowed for web safe HTML and SVG field types.
Utilities
Display Year
Display the current year according to UTC/GMT timezone:
{{%year}}
$any
Tests if any of a series of scalar values are "truthy". If true, then displays a block.
{{#$any value1 value.two}}
one scalar was true
{{/$any}}
$all
Tests if all of a series of scalar values are "truthy". If true, then displays a block.
{{#$all value1 value.two}}
all scalars were true
{{/$all}}
choose-string-map / choose-string-json
Provides a declarative way to choose one string based on another string. If it were defined programmatically it would be a series of simple if/then/else, or switch/case/default statements. This is especially useful when dealing with constant, well-defined values for a model field.
First, declare the string choices using
choose-string-map
.{{% choose-string-map message error=alert-danger *=alert-* }}
The first parameter of
message
defines a name of the string choice, which will be repeated later forchoose-string
. The remaining parameters define the mapping of input strings to output strings, with an optional capture of the input via "*".The above example outputs "alert-danger" when the string input is "error". A catch all / else case may defined with "". In the example above an input of "success" would result in "alert-success". For any of the cases, "" is available to fill in the existing string input.
Second, use the previously defined string map. The first parameter repeats the name given to the string map, and the second parameter is the string input.
{{#Messages.all}} <div class="notification {{% choose-string message this.type }}"> {{this.value}} </div> {{/Messages.all}}
Alternative Definition
choose-string-map
is the most compact way to define the choices. However if spaces are needed in the strings use JSON for the definition viachoose-string-json
.{{% choose-string-json message { "error": "alert alert-danger", "*": "alert alert-*" } }}
Placeholder Text
Use the pragmas {{% lorem-title }}
and {{% lorem-paragraph }}
to insert
placeholder text while mocking views.
Iteration
Stacklane extends Mustache iteration with several helpful utilities.
as
creates an alias for the iteration object, by
creates groups of up to 10 objects at a time,
and $first
/$last
booleans are available to detect the beginning and ending of an iteration stream/list.
as
Use 'as' to rename the internal variable available in each iteration:
{{#SomeList as item}} {{item.prop}} {{/SomeList}}
by
Use 'by' to iterate the collection in groups of N (up to 10).
This is most useful when working with various CSS grid layouts.
{{#SomeList by 2 as grp}} {{#grp as item}} {{item.prop}} {{/grp}} {{/SomeList}}
$first/$last
Duration iteration two booleans are exposed to denote whether the current element is either first or last.
{{#Things}} {{#$first}}<ul>{{/$first}} <li>{{this}}</li> {{#$last}}</ul>{{/$last}} {{/Things}}
If using
as
then use the alias as the prefix:{{#Things as thing}} {{#thing$first}}<ul>{{/thing$first}} <li>{{thing}}</li> {{#thing$last}}</ul>{{/thing$last}} {{/Things}}
Variable Imports
Mustache views may import variables which were exported by Suppliers. To import a variable (or variables) use the following pragma:
{{% import {SomeVar, AnotherVar} from 'π€' }}
After importing, {{SomeVar}}
may then be used anywhere within the Mustache view.
(As mentioned in the documentation on Suppliers, all variables exported exist in the module 'π€'.)
HTML Escaping
Mustache escapes / encodes special HTML characters by default when they
are included from a variable or model field. If {{company.name}}
is "ABC & Co.", then this will become the encoded HTML equivalent ABC &amp; Co.
.
Separation of Concerns
If there are developers on a team who are strictly working on Mustache views, it may be useful to limit their access to specific Supplier values. This can be accomplished by creating a pinned supplier.
For example, given a Mustache view of /hello/world.html
, and a Supplier file of
/hello/π€/πworld.js
, the Mustache view will only have access to
values exported by /hello/π€/πworld.js
, regardless of any other Supplier's in the path.
Private Files
Any file prefixed with an underscore _fileName.html
will never be publicly accessible, and is only used for
server side includes or similar logic (for example SASS import or Mustache partial).
Similarly any files whose direct parent folder
has an underscore such as /_files/something.html
,
will be considered private even if the files themselves are not prefixed
with an underscore.
Conditional Render
Certain HTML endpoints may have sections which take longer to load than others.
We recommending breaking up this HTML into separate fragments which are retrieved using a client-side fetch
.
The main page will then load as quickly as possible, followed by the longer loading fragments, increasing perceived responsiveness.
When using this pattern, it's sometimes useful to quickly and completely bypass the rendering of certain fragments.
This may be accomplished with the {{% Render-If variable }}
header. For example:
/fragment.html
<!--TEMPLATE mustache-->
{{% import {ShouldRender} from 'π€' }}
{{% Render-If ShouldRender }}
{{% Cache-Control-Seconds 60 }}
... HTML content if ShouldRender == true ...
When the supplied variable ShouldRender
is false, then the endpoint will return status code 200 and 0 bytes.
Cache Control
For special cases only. Stacklane already applies a Cache-Control appropriate for most scenarios.
The following client-side cache options are available for Mustache endpoints. Typically these options are only used in special cases.
Cache-Control-Seconds
Sets the "max-age" to the given seconds:
{{% Cache-Control-Seconds 30 }}
Single User Endpoints
When using built-in user functionality and forms, Stacklane automatically considers an HTML page as being generated for a single specific user/visitor.
In these cases it applies the Cache-Control:
no-cache, no-store, max-age=0, must-revalidate
When used in conjunction with Cache-Control-Seconds
, this becomes:
max-age=N, private