This document explains the language syntax and functionality of the included Modulo template system.
Templates
A "template" is the text within a Template CPart. Most Components defined within Modulo will also define a Template CPart in order to render their contents.
A template contains variables, which get replaced with values when the template is evaluated, and tags, which control the logic of the template.
Below is an example template that shows off several features of templates. Each element will be explained later in this document.
Approach
Modulo's templating language is designed to be fairly versatile in capabilities, while still feel comfortable to those only used to working with HTML. If you have any exposure to other similar template languages, such as Django (Python), Shopify's Liquid (Ruby), Hugo (Go), Jinja2 (Python), Nunjucks (JavaScript), or Twig (PHP), you should feel right at home with Modulo's templates. In fact, Modulo's templating was modeled so closely on Django's, that much of this documentation was heavily inspired by Django's own documentation. This also means that most text editors already can highlight Modulo template code: Just configure your editor to syntax highlight Django HTML templates, and you'll be all set for editing Modulo template or library files. Thanks, Django!
If you are used to frontend frameworks which permit mixing functions and
imperative code directly with DOM elements, such as with React.js or Svelte, or
using XML-based DOM construction syntax, like JSX, then there is an important
difference to note: Modulo templates are string-based, and intended to
resembled declarative mark-up. When compared to a general language like
JavaScript (which Modulo templates compile into), it's intentionally limited:
While the Modulo template language provides "template tags" that resemble
programming constructs (if
, for
, etc), it does not permit any embedded
JavaScript, but instead only a few constructs and operations.
This declarative, string-based templating paradigm was chosen with the same reason behind the MVC paradigm: "To separate internal representations of information from the ways information is presented to and accepted from the user". Supporting pure-text output enforces this clear wall of separation between presentation and business logic. Furthermore, this will be more familiar to backend developers more familiar with string-based templating in general (such as Ruby, Python, or PHP developers).
Variables
Variables look like this: {{ my-variable-name }}
. They may also look like {{
myVariable }}
. Variable names should start with an alphabetical character, and
otherwise be alphanumeric (e.g., A-Z
and 0-9
). Importantly, variable names
cannot have spaces (e.g. ), special characters (e.g.
#
), or punctuation
characters outside of underscore (_
), dash (-
, described below in Variable
syntax), or the dot (.
, described next).
Drilling down: The dot (.)
When using template variables, you often want to access data that has been structured using the various JSON data structures. That is, you might be accessing arrays of numbers for a chart you are rendering on a dashboard, or an object containing user information from a database for a profile page.
"Digging" into different data structures to unearth the data you want is a
process sometimes nicknamed "drilling down". In Modulo, the dot (.
) character
is used to "drill down", or access sub-properties of a variable. This syntax is
much like JavaScript.
In the above example, {{ state.count }}
will be replaced with the value of
the "count" property of the state data. Similarly, {{ script.data.title }}
reaches even further, by first accessing the script
variable, then the data
subproperty, then the title
subproperty.
If you use a variable that doesn't exist, the template system will insert the
value of undefined
, much like in JavaScript.
Note that "bar" in a template expression like {{ foo.bar }}
will be
interpreted as a literal string property of "foo', and not a variable "bar",
even if one exists in the template context. If you wish to to do the opposite
behavior, that is, actually resolve "bar" into it's own value, and then use
that value to access a property of bar, consider using the get
filter:
{{ foo|get:bar }}
. This is equivalent to the "square brackets" notation of
JavaScript (e.g. foo[bar]
), and thus is also useful for accessing numeric
indices. For an example, if we have an array in our state like
foo:='["a", "b", "c"]'
, then we can access "b"
as follows:
{{ state.foo|get:1 }}
.
Variable attributes that begin with an underscore should generally not be accessed, as that can be used to indicate private.
What variables are available
Variables come from the component's renderObj
that is produced in the
prepare
lifecycle phase. More on the "renderObj" in the section on
lifecycle. In practicality, this is another
way to say that most variables typically "come from" the CParts of a component.
That is, state
will provide the state values, props
will provide the values
passed down to this component, and finally script
will provide variables from
script tags. In other words, each CPart "decides" which variables it supplies
to the Template based on it's "prepare" lifecycle behavior. See the "Variables
from CParts" section below for a more thorough look at this.
Custom variables using the Script "prepare" callback
If you want to create computed variables (like in Vue.js), you can create a
custom prepareCallback
lifecycle callback in your Script CPart tag as follows.
Examine the following example:
Note that in some cases you may instead want to make a custom template filter (see below, in the section *Filter).
Variables from CParts
Each CPart within a given component may contribute a variable to the Template. Of the built-in CParts, the following contribute a variable with useful data:
- Props - If the Props CPart is present, then you will have a
{{ props }}
variable available. This gives access to the current value of props data. For example,{{ props.title }}
gives access to the current value of the "title" attribute of the component. - State - If the State CPart is present, then you will have a
{{ state }}
variable available. This gives access to the current value of state data, the object that represents state. For example,{{ state.number }}
gives access to the current value of the "number" property of state. - Script - If the Script CPart is present, then you will have a
{{ script }}
variable available. The main uses of this for access to computed variables from theprepareCallback
. Another use is to accessscript.exports
variable as a way to store global, static data shared between all instances of a component.
Variables from templatetags
We'll go over template-tags later, but some template-tags can add variables into the mix as well. As a sneak-peak, examine the following code:
Note how athlete
declares a new variable, which can be reused in
{{ athlete.name }}
. For more on for-loops, see the
built-in template-tag reference.
Variable Syntax
The quandary of kebab vs camel
In naming variables in programming languages, there are several popular syntax
options. One is nicknamed "camel case", which looksLikeThis
(kind of like
humps on a camel's back), and "kebab case", which looks-like-this
(kind of
like food on a kebab skewer). These silly nicknames might require some
imagination, but hopefully they also make it easier to remember!
The Modulo templating language supports either of these two options. It has to
be flexible since it sits at the "crossroads" of HTML, which uses "kebab" while
ignoring capital letters, and JavaScript, which respects capital letters and
thus can use "camel" syntax. For example, data-results
(kebab) is a valid
HTML attribute name, but not a valid JavaScript variable name, since -
is
forbidden. In JavaScript, it would be normal to use dataResults
(camel)
instead. However, since capitalization is ignored in both plain HTML and
Component Part definitions, ignoring case means dataResults
looks the same as
dataresults
and DATARESULTS
, so there's no way to preserve that formatting.
In other words, outside of Script CParts, we should use data-results
(kebab
case) to keep it case-insensitive and HTML-friendly.
Examples
Example #1: Automatic camel case
Examine the following example. Note how in Templating, you can use either
syntax, but in JavaScript, you can't write unquoted property names with -
characters:
Example #2: Avoiding automatic camel case
Sometimes there is data in a JavaScript object that is written using the
HTML-style kebab case. Even though it might be awkward to write like this in
JavaScript, it's pretty common to encounter this in JSON files. In this case,
the solution is using the |get
filter. It still supports .
syntax to "drill
down" to access properties, but does it does not do camel case transformation.
See below:
Syntax best practices
In Templates, use dash syntax as much as possible, since it "blends in" with the
surrounding HTML and Component Part definitions (e.g. {{ script.also-here }}
). Only use |get
as a last
resort, when the default syntax fails you (e.g. in Example #2).
In Scripts, use capitalization syntax as much as possible sicne it "blends in" with the surounding JavaScript code.
Filters
You can modify variables for display by using filters.
Filters look like this: {{ name|lower }}
. This displays the value of the {{
name }}
variable after being filtered through the lower filter, which converts
text to lowercase. Use a pipe (|) to apply a filter.
Filters can be "chained." The output of one filter is applied to the next. This
chaining feature can be applied to combine behavior of two or more filters. For
example, using a filter chain like
{{ state.color|allow:"red,blue"|default:"green" }}
is a way to conditionally
allow for only certain strings to be in state.color
, while providing a
default if none were specified.
Some filters take arguments. A filter argument looks like this: {{
state.bio|truncate:30 }}
. This will display the first 30 characters of the bio
variable, possibly appending an ellipsis if its full length exceeds that.
Filter arguments that contain spaces must be quoted; for example, to join a
list with commas and spaces you'd use {{ state.list|join:", " }}
.
Modulo's templating language provides many built-in template filters. You can read all about them in the built-in filter reference.
Common template filters
To give you an idea of what a filter might be useful for, one popular filter to
use as an example is the |default
filter. This filter shows the variable if
it has a value. However, if a variable is false, empty, or 0, it will use a
default value instead. For example, consider the following "profile card"
snippet, and summary of template filters used:
- |lower - This converts the text to lowercase, in this case showing the username in all lowercase letters
- |join - The join filter combines an array, in this case the user's names (e.g. first, middle, and last names)
- |default - Finally, the third
example shows off the usefulness of the
|default
filter, which helps you add quick default values for fields that are sometimes unspecified. In this case value, if the value of the pronouns field is a falsy or undefined value it will showing a more human-friendly placeholder message instead of justundefined
,null
, orfalse
These are just a few of the dozens of filters available. See the Template Filter reference for the complete list.
Custom filters
Registering custom filters requires little code, and can be done in a Script tags. See below for two examples of registering a custom filter:
Tags
Tags look like this: {% tag %}
(except with the word "tag" replaced with
something else). Tags can be more complex than variables: Some create text in
the output, some control flow by performing loops or logic, and some load
external information into the template to be used by later variables.
Most built-in tags require beginning and ending tags, in the format of:
{% tag %}...contents...{% endtag %}
Modulo ships with several built-in template-tags. You can read all about them in the built-in template-tag reference.
Common template tags
For examples, here is an example and summary of if and for, the two most commonly used template-tags:
{% for %}
- Summary: Duplicate a block of HTML code for every item in a collection of items (e.g. an array).- Allows a HTML code to be repeated while "looping" over each item
- Example: In this case, it displayed a list of athletes, assuming each
athlete is an Object with a property
.name
, in an Array calledstate.athletes
{% if %}
- Summary: Only shows a block of HTML code if the condition is met or the variable is a true value- Allows conditional rendering of HTML, or alternating appearances (e.g. toggling collapsed and visible for a modal pop-up or accordian component)
- Example: In this case, it will only show "Team is full (10 max)" if there are exactly 10 athletes in that Array
Again, these are just two of them, while the rest are documented here.
Contrasting template tags and template filters
The outward differences between tags and filters are the syntax and behavior:
Tags have percent-sign syntax (e.g. {% tag example %}
) and are usually for
control flow, while template filters use curly brace and vertical pipe syntax
(e.g. {{ example|filter }}
) and are usually for modifying or preparing values
for display.
There is a big underlying behavioral difference, as well. The most important difference between template tags and template filters are that template tags are interpreted at "compile-time" while filters are interpreted at "render-time".
Tags: Compile-time
"Compile time" is when the template is first loaded, no matter if it was
embedded in a <Template>
or using a -src=
). Compile time only happens once,
and will not happen ever if you are running a "build" or production version of
a Modulo component bundle.
The pluses of "compile time" involve efficiency: Your template tag code can be as complicated or inefficient as necessary, and as long as the code it outputs is efficient, the final bundled JavaScript can be lean and mean, possibly omitting the code used to generate it. The caveats are that mistakes with template tags might stop the component from rendering or compiling JavaScript at all, e.g. mistakes such as syntax typo or a buggy third-party template tag.
Filter: Render-time
"Render time" when the component is outputting it's HTML into the DOM, after it's loaded. Mistakes with filters may only be detected when it tries using it, and could even be overlooked if the filter doesn't get used, e.g. it's only within an if statement that you haven't tested.
Filters should always get included in compiled bundles, since they get run each time the component rerenders. The pluses are they are much easier to write. The caveats are they might be less efficient, depending on the situation.
Custom tags
Just like filters, you can also create your own custom template tags. Custom tags are more difficult to write, since they are run during build time (not during "run time", like filters). That means they need to generate the JavaScript code that does what you want, as opposed to do what you want directly. To register a template tag, be sure to register it at the top level, since it will be needed during build time:
Comments
To comment-out part of a line in a template, use the comment syntax: {# text in here is ignored #}
. This syntax should only be used for single-line comments. Due to this, it's not intended for "commenting out" or disabling portions of template code.
If you need to comment out a multiline portion of the template, especially a block of other template code, use the comment tag syntax instead. This looks like: {% comment %}text in here is ignored{% endcomment %}
Examples of both are below:
Debugging
Modulo templates generate JavaScript code which mirrors the logic found in the
template. Like all generated code in Modulo, it gets appended to the <head>
as a <script>
tag. When inspecting the resulting JavaScript code, note the
JavaScript comments to each line indicating the Templating text that
corresponds, so if a Modulo template is behaving unexpectedly, you can examine
the actual code getting generated.
Modulo templates also come equipped with the {% debugger %}
template tag.
This will insert a debugger;
JavaScript statement. This "freezes" your app in
time. After halting execution at the given statement, your browser's Developer
Tools will allow you to inspect the values of local variables in the generated
Template code itself. Note that the CTX
variable refers to the Template
render context (which, in the case of Modulo components, refers to the
renderObj
, or the object that has script
, state
etc. properties generated
by component parts). This is typically what you'll want to poke around on to
figure out why something is broken. Example below, with a comment hinting at a
story as to why the debugger was used:
Note: For obvious reasons, it's very important to remember to delete all your "debugger" statements before you release your code, or your app may freeze for other people as well.
Escaping
Every template automatically escapes the output of every template variable. By
default in Modulo, it escapes 5 characters by replacing each with its
corresponding HTML character entitty to prevent it from being interpreted as
code. Specifically, these five characters are escaped, since they are the ones
that are "dangerous" for HTML and URL syntax: >
(greater than), <
(less
than), '
(single quote), "
(double quote), &
(ampersand).
The purpose of escaping
What is safe? All data is considered "untrusted" by default, as in, you can't trust that it's not from some person trying to hack a user on your site, or inject spam onto a platform. It's up to the developer to choose what is "trusted". You should only mark trusted data as
|safe
. You don't want anyone trying to slip in malicious JS behavior! How to validate if a bit of HTML is safe to include verbatim (e.g. when to use|safe
) is outside the scope of this document, but in general, you can trust HTML from a trusted source, e.g. yourself or a member of your team or organization, and, sometimes, you can trust user-supplied data, when it has already been generated or validated on a backend web server or database management system, such as something that allows only certain tags of HTML and strips away anything else.
Since Modulo Templates generate HTML, which then gets intepreted into the DOM, there's there's a risk that a variable will include characters that affect the resulting HTML, by closing a tag or quotation early (or accidentally opening a new one). For example, consider this template fragment:
Imagine if a user were to enter their name as </p><h1>Big
. If the template
system were to insert that value directly, without modification, it would
result in the P tag getting closed prematurely, and a H1 tag taking over,
making the text large. Furthermore, a malicious user could even use this
vulnerability to add in JavaScript behavior (e.g. via an "onclick") that acts
on behalf of other users, such as by sending requests to an API. To avoid this
problem, Modulo uses automatic escaping.
Safe to turn off escaping
Sometimes you want to have HTML in variables, such as a Prop that accepts HTML for styling, or a string of HTML that might be user-entered, but it's loaded from a trusted source, such as a staff-only database or API. If you have HTML in a variable that that you actually intend to be rendered as HTML, then you will need to turn off escaping. To disable auto-escaping for an individual variable, you need to "mark it as safe". That is, you are saying to Modulo Templating that this value may have special HTML characters, but it's safe to assume it's valid, trustworthy HTML.
Examples
Escaping is sometimes easiest understood with examples. Try the following
demonstrations for example use of the |safe
filter.
Example #1: Escaping for tags and attributes
Note how the (1) ESCAPED
content shows the <em>
tag, since it escapes the
<
and >
symbols preventing them from being interpretted as HTML, and the
(2) SAFE
content ends the title attribute too early.
Example #2: Escaping user inputted data
For a slightly more realistic example, where user message content is marked safe, while usernames are left to be escaped, examine the following:
Direct usage of Template interface
Modulo's Template language can be used as a regular JavaScript class with the
new
constructor syntax as well. For example:
One use of this interface is enabling users to not only edit HTML (as with
|safe
, described earlier), but also be able to edit template tags themselves.
Note that this will enable users to write and evaluate (potentially buggy)
custom JavaScript code, and thus should be used with even more caution than the
|safe
filter. An example of using this to allow users to program their own
templates is below: