Templating Language - Modulo Documentation

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

Modulo's Declarative Templating 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 this documentation page was modelled on 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!

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.

Whay makes Modulo a Declarative String Templating different than JSX and/or other frameworks? - 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. 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.

Variables from Script

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

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 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 Configuration definition at the top level. 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 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.

Differences between tags and 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:

Why not HTML comments? In most code, HTML comments are available. For example, you can ignore an entire <Template> part like this: <!--<Template>...--> However, within a Template, HTML Comments are part of the template itself, and will be in the resulting DOM when the template renders. Furthermore, template code within the comment will be treated like any other template code. For example, <!-- {{ state.msg|upper }} --> will result in a comment being produced in the DOM with the uppercase value of state.msg (e.g. <!-- HELLO WORLD -->). Thus, to fully ignore HTML or template-tags within a <Template>, more types of comments are needed.

Commenting and debugging

Short comment syntax

To add a short note or comment: {# text in here is ignored #}

This syntax should only be used for short, single-line messages, and cannot contain disable portions of template code.

Comment tag

To comment out text or code: {% comment %}text in here is ignored{% endcomment %}

If you need to comment out Template code, or a multiline portion of the template, then use this comment tag syntax instead, which will work in more cases.

Examples of both types:

Debugger tag

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.

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.

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

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:

Can I force as unsafe? If a string was marked as safe and you want to "unmark" it, then simply add an empty string to add a new string: {{ state.text|add:'' }}