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.

<Template> <h1>{{ script.title }}</h1> <p>There are <em>{{ state.count }} {{ state.count|pluralize:"articles,article" }}.</em></p> {# Show the articles #} {% for article in state.articles %} <h4 style="color: tomato">{{ article.headline|upper }}</h4> {% if article.tease %} <p>{{ article.tease|truncate:30 }}</p> {% endif %} {% endfor %} </Template> <!-- The data below was used to render the template above --> <State count:=42 articles:=[] articles.0:={} articles.1:={} articles.2:={} articles.0.headline="Modulo released" articles.1.headline="The experts ask" articles.2.headline="Controversy ahead" articles.0.tease="The most exciting news of the century." articles.1.tease="Can HTML be fun again?" articles.2.tease="Why constructing JS is risky business." ></State> <Script> function prepareCallback() { return { title: "Modulo News", } } </Script>
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:

<Template> <input name="perc" [state.bind] />% of <input name="total" [state.bind] /> is: {{ script.calcResult }} </Template> <State perc:=50 total:=30 ></State> <Script> function prepareCallback() { const calcResult = (state.perc / 100) * state.total; return { calcResult }; } </Script> <Style> input { display: inline; width: 25px } </Style>

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 the prepareCallback. Another use is to access script.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.

{% for athlete in state.athletes %} <p>{{ athlete.name }}</p> {% endfor %}

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:

<Template> <p>(State) Dashes: {{ state.i-like-long-variable-names }}</p> <p>(State) Capitals: {{ state.iLikeLongVariableNames }}</p> <p>(Script) Dashes: {{ script.also-here }}</p> <p>(Script) Capitals: {{ script.alsoHere }}</p> </Template> <State i-like-long-variable-names="ok" ></State> <!-- Always incorrect: <State iLikeLongVariableNames="ok"> --> <Script> function prepareCallback() { return { alsoHere: "ok", // Always incorrect: also-here: "ok" } } </Script>

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:

<Template> STAT: {{ staticdata|get:"database-query-results.0.query-status" }} </Template> <StaticData> { "database-query-results": [ { "query-status": "OK" } ] } </StaticData>

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:

<div class="profile-card"> <p>Username: <tt>@{{ user.username|lower }}</tt></p> <p>Real name: <tt>{{ user.namesArray|join:" " }}</tt></p> <p>Pronouns: <tt>{{ user.pronouns|default:"(None specified)" }}</tt></p> </div>
  • |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 just undefined, null, or false

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:

<Template> Total * 2: {{ state.total|twice }} <hr /> A: {{ state.a|percent:state.total }}<br /> B: {{ state.b|percent:state.total }} </Template> <State a:=3 b:=13 total:=42 ></State> <Script> modulo.register('templateFilter', function twice(value) { // This custom template filter has no arguments, and // simply doubles the single expected input. return value * 2; }); modulo.register('templateFilter', function percent(value, arg) { // This custom template filter has more complicated math // that takes the percentage of the value and formats it. const num = (value / 100) * arg; return `${ num }% of ${ arg }`; }); </Script>

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:

<ul> {% for athlete in state.athletes %} <li>{{ athlete.name }}</li> {% endfor %} </ul> {% if state.athletes.length is 10 %} Team is full (10 max) {% endif %}
  • {% 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 called state.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:

<script Configuration> modulo.register('templateTag', function console_logger (text, templateCPart) { // Simply return "console.log" code to show the variable given return `console.log(CTX.${ text.trim() });`, }); </script> <Component> <Template> {% for value of state.data %} {% console_logger value %} {% endfor %} </Template> <State data:='[3, 10, 42]' ></State> </Component>

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:

<Template> <h1>hello {# greeting #}</h1> {% comment %} {% if a %}<div>{{ b }}</div>{% endif %} <h3>{{ state.items|first }}</h3> {% endcomment %} <p>Below the greeting...</p> </Template>

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:

{% for item in state.data %} {{ item.user_id }} {# Bug: Keeps on coming back as "undefined" #} {% debugger %} {% endfor %}

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:

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

<Template> <p>(1) ESCAPED: {{ state.content }}</p> <p>(1) SAFE: {{ state.content|safe }}</p> <p>(2) ESCAPED: <tt title="{{ state.content }}">HOVER</tt></p> <p>(2) SAFE: <tt title="{{ state.content|safe }}">HOVER</tt></p> </Template> <State content='There is no "there" <em>there</em>' ></State> <Style> p { font-size: 0.8rem; } </Style>

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:

<Template> <p>User "<em>{{ state.username }}</em>" sent a message:</p> <div class="msgcontent"> {{ state.content|safe }} </div> </Template> <State username="Lil' <Bobby> <Tables>" content=' I <i>love</i> the classic <a target="_blank" href="https://xkcd.com/327/">xkcd #327</a> on the risk of trusting <b>user inputted data</b> ' ></State> <Style> .msgcontent { background: #999; padding: 10px; margin: 10px; } </Style>

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:

const { Template } = modulo.registry.cparts; let myTemplate = new Template('Hi {{ msg }}!'); console.log(myTemplate.render({ msg: 'there' })); // "Hi there!"

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:

<Template> <input [state.bind] name="data.name" /> <input [state.bind] name="data.user" /> <textarea [state.bind] name="template"></textarea> <div>{{ script.results|safe }}</div> </Template> <State template="Hello <em>{{ name }}</em><br />Username: <tt>{{ user }}</tt>" data:={} data.name="Pedro" data.user="pramirez" ></State> <Script> const { Template } = modulo.registry.cparts; function prepareCallback() { const myTemplate = new Template(state.template); const results = myTemplate.render(state.data); return { results }; } </Script>