Templating
Definition
A domain-specific language and deployment process designed specifically to generate custom text output.
Example Use Cases
A web content management system generates web pages. The HTML is generated from a set of templates, separate from the source code.
An email marketing system uses templates to form the body of an email. The template is edited through a UI and allows the editor to enter placeholders that are replaced with specific data.
Relation to the Application
Templating is often a separate subsystem, defined along several characteristics:
Language: Templates are usually in a language other than the core language of the application. This language has specific features designed for outputting custom text.
Computational Space: Template code is often executed in a “sandboxed” computational space, separated from the main application. Errors in templates are (or should be) contained and managed without a negative on the stability of the application.
Code Management: Template code is often managed separately from other integration code. It’s sometimes in files in a different storage mechanism, and is sometimes managed directly from the application UI through a browser-based IDE.
Intended Developer: A “template developer” is sometimes a separate role than an “integration” or “backend” developer. This developer is usually skilled in front-end technologies (HTML, CSS< JavaScript) and also learns the templating tools of the application.
Sometimes, of course, none of these are true. An application might do templating in the same language as the application, executed in the same process, with files in the same location, coded by the same developer. The differences are a matter of degree.
A key point: the output of a templating operating is usually intended as a final stage of delivery, to be sent to a consuming device or platform. Very rarely is the output of templating re-incorporated into application logic or operated on in any other way. Data is templated at the last moment before delivery, then it leaves the application context.
The Two Philosophies of Templating
There are two competing theories of how templates should interact with the application:
Weak Templates: Templates should be limited in both data and features. They should only be able to interact with data “given” to them, and they should have a very limited logical featureset. Having logic inside templates just creates another level of code and potential issues, often written by a developer not familiar with the larger application.
Strong Templates: Templates should have most of the logical features of the “full” programming language, and should even be able to retrieve data beyond that which is provided to them. A templating language only exists to provide a layer of computational sandboxing, features specifically designed for the manipulation of text, and the convenience of a separate DevOps context.
Most systems will exist somewhere in the middle. In particular, the weak templating philosophy has some vocal adherents for “logic-less” templates, but it imposes considerable limitations on template development that often frustrate developers working with it.
The Template “Context”
Most all templating languages operate on the idea of a special “sandbox” or context in which the template executes. The templating language will have access to the data specifically injected into this context, or the results of functions made available to that context, and nothing else.
context.SetValue("name", "Deane");
template.Render(context);
In the example, a token called “name” will be available inside the template, containing the string value “Deane.”
In some cases, selected data retrieval functions might be available to templates, but this is not common. “Extra” data might be made available to a template, which it can use or ignore, but seldom can a template proactively retrieve data from outside the context.
Tag Structure of Templating Languages
Templating languages are usually designed to be simpler than “full” programming languages. They’re also designed to be intermixed with “markup” of some kind – usually HTML. This means that template code is usually intermixed with markup code, offset by delimiters.
Liquid and Twig use
{% %}
for logic blocks, and{{ }}
for output blocksScriban uses
{{ }}
for everythingHandlebars and Mustache also use
{{ }}
for output, with the addition of some other tags like{{# }}
and{{ / }}
for “sections,” which allow looping and simple conditionals
Anything in these tags is either executed (and removed) or replaced with something else (and therefore also removed). Anything outside these tags is part of the generated output.
There are also a few templating languages that abandon the “intermixed tag” formal altogether:
HAML (HTML abstraction markup language) uses an tree-style markup format with indenting to represent nesting
TAL (Template Attribute Language) uses attributes on HTML tags
XSLT uses XML to template other XML
Feature: Token Replacement
As templating code is intermixed with markup code, much of templating involves the insertion of placeholders or tokens which are replaced with data injection into the templating context.
<p>
<strong>First Name:</strong> {{ person.first_name }}
</p>
In the above example, a data object called person
has been injected into the template context. It has a property called first_name
which will be retrieved and will take the place of {{ person.first_name }}
Feature: Filters
In many cases, data needs to be formatted or modified before it’s output. The most common metaphor for this is the concept of “piping” data in a command line using the |
character.
<p>
<strong>First Name:</strong> {{ person.first_name | upcase }}
</p>
In that example, the output of person.first_name
will be filtered through a filter called upcase
, which will presumably convert the text to upper case.
In most systems, filters can be chained and can sometimes take arguments:
{{ person.age | plus:10 | format_number:'N0' }}
Feature: Conditionals
Logic in templates might branch, though it’s much more common to use conditionals to simply display or not display a particular chunk of markup:
{% if person.age >= 55 %}
<p>You're legible for a senior discount!</p>
{% endif %}
Most templates will support the standard operators: equal, not equal, greater than, lesser than, etc. Some also have a simple negation tag, like unless
, which would be the opposite of if
.
Given that most templating languages are less exact than full programming languages, and, in particular, less strict about typing, sometimes it’s difficult to determining “nothing-ness.” Does this mean…
- A
null
value - An empty string
- An empty collection
- A numeric value of
0
Every language is little different in this respect. Some include constants like blank
, empty
, and nil
to test against:
{% if articles == empty %}
Feature: Looping
Most languages can iterate over collections of objects.
<ul>
{% for article in articles %}
<li>{{ article.title }}</li>
{% endif %}
</ul>
Some languages include extra iteration features like:
- Limiting loops to a specified number of iterations
- An
else
block to provide a markup option if the collection is empty - A special
forloop
variable inside the loop which contains information about the loop iteration: the number, whether its odd or even, the first or last, etc. - The ability to “cycle” an array – use a different element on each loop (for example: you might provide an array of five CSS classes, and the cycle variable would move between the five on each successive loop, starting back at the first on loop six)
Feature: Inclusion
Like other code, templates tend to be repetitive and derivative, and it’s often that template code is re-used. Therefore, templates can often perform inclusion on two levels
Sub-template Inclusion: Template A can include Template B. It might even be able to provide specific data to Template B that can only be accessed and used by Template B. Sometimes it can include and execute Template B for each iteration of a loop.
Super-template Inclusion: Template A can “include itself” into Template C. Additionally, Template A might be able to divide itself into different sections, and include the output of those sections into specific places in Template C. This is usually done to generate page-specific output (Template A), then “wrap” that output in a master layout (Template C).
Many templates will perform both types of inclusion:
article.tpl
includeslist_item.tpl
inside a loop- The result of that operation is then included into
text_page.tpl
text_page.tpl
includesopen_graph.tpl
andpdf_download_link.tpl
- The result of all that is then included into
master_layout.tpl
Common Templating Systems
Given that templating is pan-application, most languages have a “favored” templating system:
- .NET: Razor
- Java: Freemarker and Velocity
- PHP: Twig
Some other systems have achieved pan-language adoption, with implementations for multiple languages:
- Liquid
- Mustache (crucially, this can be used in client-side JavaScript)
- Handlebars
- StringTemplate
Very rarely will an application write a custom templating system. It’s much more common for a language to provide custom extensions to an existing language.
Extensibility
Most templating languages allow new functionality to be added via development in the implementing language. Common extension points:
- Custom tags and blocks
- Custom formatting filters
- Custom functions
These manifest as custom strings in template code that invoke code in the native application language and send return the results to the template context.
Practices for User Extensibility: Templating
Avoid generating output from core application code. Rather, generate output in a separate templating system to ease the customization of output.
Use an existing templating system that has achieved an independent level of adoption
Allow templates to exist in a location separate from the main application files, to allow for a different DevOps workflow
Abstract template retrieval for the application, allowing the user to specify alternate repositories for templates to adapt to their workflow
If appropriate, allow online editing and provide a capable browser-based IDE
Consider allowing templating overriding. Have the application check Template Repository A, and if it doesn’t find anything, check Template Repository B, etc. This allows for a “default” set of templates which can be overridden by a more specific set based on use case