Before reading this, read the overview on rendering
In that overview we included a layout template where it would be excellent if some of the code snippets were reusable between multiple layouts:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title | escape }}</title>
{% for style in stylesheets %}
<link rel="stylesheet" type="text/css" href="{{ style | escape }}"/>
{% endfor %}
{% for js in jshead %}
<script src="{{ js | escape }}"></script>
{% endfor %}
</head>
<body>
<head class="container-fluid">
<h1>{{ title | escape }}</h1>
</head>
<div class="container-md">
<div class="row">
<article class="col">
{{ content }}
</article>
<section class="col-2">
<!-- Navigation sidebar goes here -->
</section>
</div>
</div>
{% for js in jsbottom %}
<script src="{{ js | escape }}"></script>
{% endfor %}
</body>
</html>
There is a section for rendering CSS stylesheet links, JavaScript links in both the header and page bottom, and a navigation sidebar. In a large complex site there will likely be multiple layout templates, with lots of common elements in use across the site.
In other words, it's desirable to have code reuse between layout templates. That's what partial templates support.
We've talked about partial templates, but have not shown how how to implement them. This is a powerful tool for sharing code snippets from one page to another, or for dynamically computing the layout of some data.
Let's example how to implement support for partial templates. By the bottom of this page that layout template will be much cleaner by using several partials.
Partial template support in the Renderers package
The Renderers package supports two functions, renderers.partial
and renderers.partialSync
. These functions are given a template file name, and a metadata structure, and locate the template, then call either render
or renderSync
.
The difference is, as the function names imply, to use synchronous operations or not. Some execution contexts require synchronous execution, while others can deal with asynchronous.
There is a default implementation for these functions that works out of the box. However, your application may require a custom implementation, such as a custom algorithm for finding partial templates. For that purpose you can assign a custom function like so:his:
renderers.partialFunc =
async (fname: string, metadata: any): Promise<string> => {
// custom function code
};
renderers.partialSyncFunc =
(fname: string, metadata: any): string => {
// custom function code
};
After making this assignment, the application-provided functions will be used instead of the default functions.
Using partials from a typical template engine
Most of the template engines support invoking an function supplied by the application in the metadata. Therefore, a simple way to use partial
and partialSync
from such a template engine is to assign functions to the metadata.
In GuideCMS, that is done as so:
context.metadata.partial = async (fname, metadata) => {
return renderers.partial(fname, metadata);
};
context.metadata.partialSync = (fname, metadata) => {
return renderers.partialSync(fname, metadata);
};
In AkashaCMS, it is done roughly the same way.
Once this is set up in your application, a template can do this:
<section class="col-2">
{{ partialSync('sidebar.html') }}
</section>
This example uses Nunjucks syntax. Of course each template engine has its own syntax for rendering values, and further each has its own behavior around this feature. Finally, some of the template engines have built-in support for rendering templates that must be considered.
Support for partial templates in every engine
The Renderers package tries to implement the same experience in every template engine. However, this was not possible in regards to rendering partial template. Additionally, Renderers provides configuration to accommodate template engines innate ability to render templates.
Using partials in EJS templates
The EJS engine supports writing JavaScript code inside the template, and therefore can call application-provided functions, but those functions can only use synchronous execution. Additionally it has a built-in function, , that can render EJS templates.
<%- partialSync('sidebar.html') %>
<%- partialSync('example.html.njk') %>
<%- partialSync('example.html.njk', {
data: 'value'
}) %>
These examples show calling the partialSync
function either with arguments, or without arguments.
<%- include('sidebar.html') %>
<%- include('to-include.html.ejs') %>
<%- include('to-include.html.ejs', {
data: 'value'
}) %>
These examples show the equivalent using . Note that
only supports EJS templates, while
partialSync
can use any supported template format.
For passing data, EJS has a locals
variable which is the data provided to the template execution. In this case we have instead constructed custom data objects.
Using partials in LiquidJS templates
The LiquidJS engine does not support the partial
or partialSync
functions. This means we cannot implement full partialSync
support.
There is a built-in render
function which supports either .html
or .liquid
templates. This allows values to be passed.
{% render 'hello-world.html' %}
{% render 'example.html.liquid', message: 'Hello World' %}
Using partials in Handlebars templates
The Handlebars engine does not support calling partial
or partialSync
functions. It does have a syntax for partials, but that feature requires pre-registering each partial making it useless for use by the Renderers package.
To fix those two issues, the Renderers package implements a so-called Helper to support calling partialSync
.
{{partialSync template="showmessage.html.njk" message="non-block helper"}}
{{partialSync template='showtitle.html.ejs'}}
The template
parameter must be given so that it is known which template to render. Other data may be passed using the varname=value
syntax.
{{partialSync template='hello-world.html'}}
We can also include a simple HTML file.
Handlebars also supports a "block" mode, where there is an opening and closing tag, and the content in-between is the partial body.
{{#partialSync template='partial-body.html.njk'}}
PARTIAL BODY
{{/partialSync}}
{{#partialSync template='partial-body.html.njk'}}
Before nested message {{#partialSync template="showmessage.html.njk" message="NESTED MESSAGE"}}
{{/partialSync}}
{{/partialSync}}
The body of the partialSync
block is available to the template as partialBody
. THat means, partial-body.html.njk
could be this:
<strong id="strong">{{ partialBody }}</strong>
The syntax also supports nesting partialSync
invocations.
Using partials in Nunjucks templates
The Nunjucks engine supports calling an application-specified function. It does not support executing asyncronous functions. It provides its own function. Therefore, the result is that usage is very similar to what we discussed with EJS templates:
{{ partialSync('sidebar.html') }}
{{ partialSync('example.html.ejs') }}
{{ partialSync('example.html.ejs', {
data: 'value'
}) }}
{{ include('sidebar.html') }}
{{ include('to-include.html.ejs') }}
{{ include('to-include.html.ejs', {
data: 'value'
}) }}
Simplifying the page layout template shown at the top
After all that discussion, let's look at a concrete example:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title | escape }}</title>
{{ partialSync('stylesheets.html.njk', {
stylesheets: stylesheets
}) }}
{{ partialSync('jshead.html.njk', {
jshead: jshead
}) }}
</head>
<body>
<head class="container-fluid">
<h1>{{ title | escape }}</h1>
</head>
<div class="container-md">
<div class="row">
<article class="col">
{{ content }}
</article>
<section class="col-2">
{{ partialSync('sidebar.html') }}
</section>
</div>
</div>
{% include 'jsbottom.njk' %}
{% include 'hljs-init.html' %}
</body>
</html>
This is a Nunjucks template with nearly identical functionality to the template at the top. But it uses both partialSync
and as discussed earlier. We added another template including a JavaScript snippet required by HighlightJS.
This is stylesheets.html.njk
:
{% for style in stylesheets %}
<link rel="stylesheet" type="text/css"
href="{{ style | escape }}"/>
{% endfor %}
In testing it was found that, with Nunjucks, we have to explicitly pass any required data to the template. This template has the same code as the original template.
This is jshead.html.njk
:
{% for js in jshead %}
<script src="{{ js | escape }}"></script>
{% endfor %}
The exact same is said here.
This is sidebar.html
:
<div class="btn-group-vertical" role="group"
aria-label="Navigation sidebar">
<a href="index.html" class="btn btn-primary">Overview</a>
<a href="setup.html" class="btn btn-primary">Getting Started</a>
<a href="rendering.html" class="btn btn-primary">Rendering</a>
<a href="renderers.html" class="btn btn-primary">Renderers</a>
<a href="configuration.html" class="btn btn-primary">Configuration</a>
<a href="configuring-markdown.html" class="btn btn-primary">Configuring Markdown</a>
<a href="guidecms.html" class="btn btn-primary">GuideCMS</a>
<a href="api/index.html" class="btn btn-primary">API</a>
</div>
This uses Bootstrap v5 classes to make a nice-looking sidebar.
This is jsbottom.njk
:
{% for js in jsbottom %}
<script src="{{ js | escape }}"></script>
{% endfor %}
In this case the template used rather than
partialSync
. If partialSync
had been used, the file name would have to be named with a double-extension. Instead we could use a simple .njk
file extension.
This is hljs-init.html
:
<script>
try {
hljs.initHighlightingOnLoad();
} catch (err) { }
</script>
We're often given JavaScript blocks to add to a page, such as this. This is allows us to easily use the same snippet on every page, while easily updating it if any changes are required.