Structured Document Templating

Documents are templated via fragments of Liquid (via Fluid) that are stored in hidden sections of the document itself. The document editor can create template sections to customize a specific document, or they will be automatically added based on the document’s path.

Document Template

The base template for the document is stored in a section called _template. The templating engine looks for this section, and passes the document into it.

At its core, it does this:

{% for section in doc.Sections %}
    {{ section | process }}
{% endfor %}

That template iterates every section and calls the process filter on each one. That filter will then try to find a template for each section based on its name, and process it individually.

The resulting document will therefore be the combined template rendering of each section, in a series.

Section Templates

The document can contain templates for specific section names. When processing a section, the engine will look for the first section called _template:SECTIONNAME. If it finds one, it will pass the section and document to it, and capture the output.

In Fluid:

For example:

\[[ _template:movie ]]

<p>
 <strong>{{ section.Meta.title }}</strong> ({{ section.Meta.year }})
 <br/>
 {{ section.Content | markdown }}
<p>
<p>
 There are {{ doc.FilteredSections.size }} movies in this document.
</p>

This template would be located and applied to any section with the name “movie.”

Remember that the engine will locate the first template with that name. This means that the _template:movie template can be overridden by another section called _template:movie higher up in the document.

Example

Consider this document:

title: My Document

This is my document.

\[[ author ]]
name: Deane Barker
date: 2022-01-29
\[[ _template ]]

<h1>{{ doc.Title }}</h1>

{% for section in doc.Sections %}
    {{ section | process }}
{% endfor %}

\[[ _template:author ]]

<p class="byline">
By {{ section.Meta.name }} on {{ section.Meta.date | format_date:"MMMM d, yyyy" }}
<p>

\[[ _template:* ]]

{{ section.Content | markdown }}

Remember, we added these to the bottom of the document, so they exist in the document itself.

When the templating engine processes the document, it will execute the Liquid code in _template. This will print the document title in an H1 tag, then begin to iterate the sections. (Remember, the title of the document is just taken from the first section. )

The first section has an implied name of text (remember, the first section gets the default section name). The engine looks for a section named _template:text. Not finding one, it falls back to _template:* (the catchall for everything not otherwise found), and outputs the section content processed by Markdown. (In this case, it will just put P tags around the content.)

The next section has a name of author, so the engine finds the section called _template:author and passed both the section and the document itself to it (you’ll sometimes use the document in template, but not often – it’s there if you need it). This prints a paragraph of text with the author’s name and the date.

The result would look like this:

<h1>My Document</h1>

<p>This is my document.</p>

<p class="byline">
By Deane Barker on January 29, 2022
</p>

To clarify the template naming:

Reordering Sections

…don’t.

There’s no functionality for this. Not because it can’t be done, but just that I haven’t had a need to do it. In general, order the document’s sections in the order you want them to be output.

The StructuredText format is not meant to be a database. It’s a collection of document pieces with a specified, editorial order.

Technically, the sections are a collection (see How does the system's API support the model?) which turns into an ArrayValue in Fluid. So they can be ordered in a limited fashion, like this:

{% for section in doc.FilteredSections | sort:"name" %}
  {{ section | process }}
{% endfor %}

However, there’s no way to order by meta values or anything more sophisticated.

So, again, order the sections in the document in the order they’re supposed to be output. If you need the sections dynamically sorted at runtime, then this might not be the right tool for the job.

The Default _template Section

Here is the actual contents of _template on this website (it was simplified above).

<article>

{{ render("header") }}

{% if doc.Start != nil %}
    {{ doc.Start | process }}
{% endif %}

{% for section in doc.Sections %}
    {{ section | process }}
{% endfor %}

{% if doc.End != nil %}
    {{ doc.End | process }}
{% endif %}


</article>

The header include is a fairly complex template that builds the top of the document (the title, subtitle, author, date, and tags, if they exist).

The next section renders the section called _start (if found) using the template _template:start (if found).

Then the all the sections in the document are iterated and processed by their template if found, and by the catchall _template:* if not.

The next section renders the section called _end (if found) using the template _template:end (if found).

The Catchall Template

If not template is found for a section, then it’s assumed to be general text with no meta, and is rendered by a template called _template:*.

{{ section.Content | markdown }}

This is item #3 in a sequence of 4 items.

You can use your left/right arrow keys to navigate