Handlebars
Handlebars is a template system that rdfpub uses
to generate HTML pages. Template files that you create are filled out with the
results of your SPARQL queries and rendered as HTML. It is expected that you
know how to write HTML and that you understand how Handlebars operates in order
to create functioning templates.
About index files
A file named index.handlebars
is known as an index file. A resource that
has an index file will serve HTML rendered from its index file. For example,
this web page is generated from an index.handlebars
file in the
/tutorial/lessons/handlebars-templates
directory of this site's source.
About partial templates
It is often the case that many resources will have similar or identical layouts
which can be tedious to repeat throughout many index files. To make repeated
templates easier to read, write, and maintain, rdfpub makes use of a Handlebars
feature known as partials.
Handlebars partials are
templates that can be called from and included in other templates. They present
a means of building templates that include common template components that
would otherwise require that you copy/paste the parts that are shared between
multiple templates. Duplicating common code is difficult to maintain and prone
to error; partials solve this problem very gracefully.
In rdfpub, any .handlebars
file that has a name other than index
will get
registered as a partial template that other templates can include. For example,
the lesson.handlebars
file
for this tutorial site is registered as a partial named lesson
. This partial
then gets called in each of the individual lesson index templates as
{{>lesson}}
.
As you might have guessed, partial templates are inherited by subdirectories in
the same was as SPARQL query files. That is, partial templates are recursively
copied down from its own directory to all subdirectories during the site build
process.
SPARQL query results in Handlebars
In the previous lesson, it was explained
that SPARQL queries are executed against every resource they apply to, and that
the name of each SPARQL query is used to reference its results in Handlebars
templates. The resulting data object that gets passed into a Handlebars
template is indexed by each query name. For example, like so:
{
"person": { ... },
"friends": { ... }
}
And the value mapped to each name is the JSON SPARQL query results returned
from being executed for the current resource.
SPARQL query results shortcuts
The default format of a
JSON SPARQL query results
object is a little verbose and unwieldy. For example, if you wanted to
reference a name
variable from your person
query, it might look like this:
<p>This person's name is {{person.results.bindings[0].name.value}}.</p>
This is terribly long-winded which makes it harder to understand as well as
makes it more likely that you'll make some kind of human error while typing it
all out. It also makes your templates less readable.
To ease this burden, rdfpub adds a few convenient shortcuts to your query
results that make your templates easier to both read and write. Each shortcut
is explained below.
Top-level variable values
In many cases, your queries will return exactly one result which contains
information about a single resource. For instance, each lesson in this tutorial
is rendered from
a lesson
query
that extracts the name, content, chapter number, and next lesson from each
lesson. In
the lesson
template,
the results of this query are used to render each lesson as HTML.
So for this common case, it would be nice to be able to access the single
result of the query without having to drill all the way down into the SPARQL
results object. rdfpub enables this by mapping each variable to the top-level
query result object to its value in the first query result binding.
For the lesson
query, that means that instead of this:
<header>
<h1>Chapter {{lesson.results.bindings[0].chapter.value}} - {{lesson.results.bindings[0].name.value}}</h1>
</header>
We can instead do this:
<header>
<h1>Chapter {{lesson.chapter}} - {{lesson.name}}</h1>
</header>
This second example is much more concise.
Top-level results iteration
There are cases where your query might return a number of results that you need
to render as a list or such. For example,
the lessons
query
gets a list of all lessons in chapter order so that they can be rendered in a
consistent order as links at the top of each page.
It would be nice to loop through a query's results without having to dive too
deeply into the query results object. rdfpub enables this by making each query
iterable by a Handlebars #each
block helper at the top level. It also binds
each value
of a variable binding to the variable binding object itself.
So instead of a verbose iteration like this:
<ol>
{{#each lessons.results.bindings}}
<li><a href="{{url.value}}">{{name.value}}</a></li>
{{/each}}
</ol>
We can instead do this:
<ol>
{{#each lessons}}
<li><a href="{{url}}">{{name}}</a></li>
{{/each}}
</ol>
This makes iteration much more simple and sensible.
Checking if a query has results
Sometimes you may want to render a certain part of the page only if a
particular query actually has results. With a raw JSON query results object,
you could check the bindings array like so:
{{#if friends.results.bindings}}
<h2>Friends</h2>
<ul>
{{#each friends}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/if}}
rdfpub makes this kind of check more concise at the top level which allows you
to check for query results like this instead:
{{#if friends}}
<h2>Friends</h2>
<ul>
{{#each friends}}
<li>{{name}}</li>
{{/each}}
</ul>
{{/if}}
In this example, if the friends
query has no results, then the #if
check
will evaluate to false
and skip rendering the "Friends" section.
Helpers
The functionality of Handlebars can be extended via helpers which can perform
processing on information passed into a Handlebars template. rdfpub defines a
small handful of useful helpers that you can leverage for your site. In the
future, it will be possible to register your own helpers, but only the built-in
helpers described below are currently available.
any
The any
helper is used in an #if
condition to check if any single condition
of multiple conditions are true. It is effectively a logical OR between
multiple conditions.
You can use the any
helper like so:
{{#if (any friends family)}}
<p>{{!-- render any available friend or family relationships}}</p>
{{/if}}
all
The all
helper is used in an #if
condition to check that all provided
conditions are true. It is effectively a logical AND between multiple
conditions.
You can use the all
helper like so:
{{#if (all person.firstname person.lastname)}}
<p>Person has both a first name and a last name</p>
{{/if}}
equals
The equals
helper tests a number of values (usually two) for equality. This
can be useful for conditionally rendering parts of a page based on comparisons
between values.
For example, you could have a template that has different sections based on a
person's primary language returned in a query result, like so:
{{#if (equals person.language "en")}}
{{!-- English text --}}
{{/if}}
{{#if (equals person.language "es")}}
{{!-- Spanish text --}}
{{/if}}
markdown
The markdown
helper renders its Markdown
input as HTML. This can be very useful for embedding rich descriptions of
resources in your RDF data that can be converted to HTML for display on your
web pages.
This tutorial site uses markdown embedded in RDF for most of its content. For
example,
the lesson
template
renders its main content like so:
<main>
{{{markdown lesson.content}}}
</main>
Note the use of triple curly braces, a Handlebars syntax that preserves
characters that are used in HTML. Without them, the Handlebars processor would
escape the resulting HTML which would not display nicely in a web browser.
Markdown rendering is provided by
markdown-it.
relative
The use of relative URL's in HTML link
/a
elements can be very useful for
site portability because a relative link will resolve against the current base
URL which can change based on where a site is being served from. For instance,
if your data's base URL is http://www.foo.example
but your site is actually
served from http://www.bar.example
, then all of the URL's from your data that
are linked to on your HTML pages will be wrong. The links could even break with
a simple difference between http://
and https://
.
The relative
helper solves this problem by taking a URL and outputting its
path, query, and fragment/hash. This URL segment can be used in HTML as a
relative link to another page on the site without regards to where the site is
actually hosted.
Use of this helper can be seen in
the header.handlebars file
of the rdfpub tutorial site itself. Note that the base URL of the site is
https://rdf.pub
, so if the results of
the lessons query
were to simply output URL's like so...
{{#each lessons}}
<a href="{{url}}">{{name}}</a>
{{/each}}
...then the links in the header would always point to the live site at
https://rdf.pub/tutorial. If you run the site on your local machine and
access it via http://localhost/tutorial, such as for testing or
experimenting with changes, then the links will take you away from your local
site.
So the template uses the relative
helper to render relative links that will
always link to the site itself no matter where it's deployed to:
{{#each lessons}}
<a href="{{relative url}}">{{name}}</a>
{{/each}}
It is generally recommended to use the relative
helper for all URL's returned
by queries that are expected to link exclusively to other pages on the same
site.
Special variables
A couple of special variables are made available as part of the data object
passed to your Handlebars templates. These variables are each explained below.
$resource
The $resource
variable is the absolute URL of the current resource, resolved
against the BaseURI
specified in your .rdfpub configuration file. It can be
useful for appending links in order to build links to other resources,
especially language variants of the same resource.
For instance, if you have a resource http://example.com/foo/bar
that you
want to link to language variants of, you could do so in a generic/reusable
fashion like so:
<a href="{{relative $resource}}@es">Visit this page in Spanish</a>
<a href="{{relative $resource}}@de">Visit this page in German</a>
$language
The $language
variable contains the language code of the page that is
currently being processed during site rendering. As explained in
the i18n lesson, each of your index templates can
have an associated language, such as en
for English or es
for Spanish. Your
site will have a separate page for each supported language, and you can access
the language for each page using the $language
variable.
An example use case would be selecting different sections of a page based on
the current language, like so:
{{#if (equals $language "en")}}
<h1>Hello!</h1>
{{/if}}
{{#if (equals $language "es")}}
<h1>Hola!</h1>
{{/if}}