Modulo Templating Language

This document explains the language syntax and functionality of the included Modulo template system.

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!

Philosophy

In approach, Modulo's tempting language joins the ranks of the "Djangolike" templating languages: Intentionally limited, and resembling markup more than programming code. If you're used to languages which permit mixing programming code directly with DOM elements, such as with React or Svelte, you'll want to bear in mind that the Modulo template system is not simply JavaScript embedded into HTML. This is by design: the template system is meant to express presentation, not program logic.

The Modulo template system provides "tags" which function similarly to some programming constructs—an if tag for boolean tests, a for tag for looping, etc.—but the template system will not execute arbitrary JavaScript expressions. Only the tags, filters and syntax listed here is supported by default (although you can add your own extensions to the template language as needed).

Why use a text-based template instead of an XML-based one (like React's JSX), or an attribute DOM-based one (like Vue, Angular, or Riot)? 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).

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 a minimal template that illustrates a few basics. Each element will be explained later in this document.

Variables

Variables look like this: {{ variable }}. When the template engine encounters a variable, it evaluates that variable and replaces it with the result. Variable names consist of any combination of alphanumeric characters and the underscore ("_") but may not start with an underscore, and may not be a number. The dot (".") also appears in variable sections, although that has a different meaning, as indicated below. Importantly, you cannot have spaces or punctuation characters in variable names.

The dot (.)

Use a dot (.) to 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.exports.title }} reaches even further, by first accessing the script variable, then the exports 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 prepare lifecycle callback in your Script CPart tag as follows. Examine the following example:

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:

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.

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, like in {{ state.color|allow:"red,blue"|default:"green" }}, which 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 several dozen built-in template filters. You can read all about them in the built-in filter reference. To give you a taste of what's available, here are some of the more commonly used template filters:

Common filters

default

If a variable is false or empty, use given default. Otherwise, use the value of the variable. For example:

{{ value|default:"nothing" }}

If value isn't provided or is empty, the above will display "nothing".

length

Returns the length of the value. This works for both strings and lists. For example:

{{ state.articles|length }}

If state.items is ['a', 'b', 'c', 'd'], the output will be 4.

Again, these are just a few examples; see the built-in filter reference. for the complete list.

You can also create your own custom template filters; see Custom template tags and filters.

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

Here are a couple commonly used template-tags:

for

Duplicate a block of HTML code for every item in a collection of items (e.g. an array). This allows a HTML code to be repeated while looping over each item in an array. For example, to display a list of athletes, assuming one was stored as an array of objects within state.athletes:

if

Evaluates a variable, and if that variable is "true" the contents of the block are displayed:

In the above, if state.athletes is not empty, the number of athletes will be displayed by the {{ state.athletes|length }} variable.

You can also use filters and various operators in the if tag:

Final notes on tags

The most important difference between template tags and template filters are that template tags are interpreted at "compile-time" (when the template is first loaded or when you are building a JavaScript build or bundle), while filters are interpreted at "render-time" (when the component is outputting it's HTML into the DOM). This means that mistakes with template tags might stop the component from rendering at all (mistakes such as syntax typo or a buggy third-party template tag), while 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.

Also, while the above examples work, be aware that some template filters return strings, so mathematical comparisons using filters will generally not work as you expect. |length is an exception. You can use |number in other cases.

Finally, just like filters, you can also create your own custom template tags; see Templating Reference.

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 instead 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

By default in Modulo, every template automatically escapes the output of every variable value. Specifically, these five characters are escaped:

Why escape

When generating HTML from templates, there's always a risk that a variable will include characters that affect the resulting HTML. For example, consider this template fragment:

<p>Hello, {{ state.name }}</p>

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.

Clearly, user-submitted data shouldn't be inserted directly into your Web pages, at least without some sort of validation. A malicious user could even use this vulnerability to add in JavaScript behavior that acts on behalf of other users, such as by sending requests to an API. To avoid this problem, Modulo uses automatic escaping.

How to turn it off

Sometimes template variables contain trusted data that you actually intend to be rendered as raw HTML, in which case you don't want their contents to be escaped. For example, you might want to render a string of HTML in your state that was generated by JS on the front-end, or a string of HTML loaded from a trusted source, such as a staff-only database.

To disable auto-escaping for an individual variable, use the safe filter:

This will be escaped: {{ state.content }}
This will not be escaped: {{ state.content|safe }}

Think of safe as shorthand for safe from further escaping or can be safely interpreted as HTML. In this example, if state.content was defined like content="<b>"', the output will be:

This will be escaped: &lt;b&gt;
This will not be escaped: <b>

Keep in mind that 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. with |safe) is outside the scope of this document, but generally you should only trust HTML that has been validated on the backend or database, whitelisting only certain features of HTML and checking for "red flags" like embedded JavaScript code, or if it's a "staff-only" feature like a team blog, and thus the content generated is only editable by a trusted source (e.g. yourself or a member of your team). Examine the code below for a concrete example of how this escaping behaves: