2 Writing Templates
2.1 Inserting Values
Here is a simple example of a template for generating an HTML page:
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
Today's date: ${date}
</body>
</html>
This template expects two pieces of data: a title and a date. A dollar sign followed by
an expression in braces is called an expansion; it means, `insert the value of this
expression here'. If the value of title was Important Page, and the value of
date was 29 September 2003, the resulting document would look like this:
<html>
<head>
<title>Important Page</title>
</head>
<body>
<h1>Important Page</h1>
Today's date: 29 September 2003
</body>
</html>
A variable such as title or date is called a scalar variable, meaning
that it contains only one value.
If an expansion contains a value that has not been set, it produces no output. This makes
it convenient to write HTML form elements that display the value previously entered, if
any:
<input type="text" name="address" value="${address}"/>
2.2 Hashes
It is often convenient to group several related values together, and give a name to the
group. A hash is a collection of values, each of which has a name (called a `key').
Continuing with the example from the previous section, we might want to break down the
date into day, month and year components:
Today's date: ${date.day}-${date.month}-${date.year}
Here, date is a hash, which contains three scalars: day, month and year. (An expression like date.day is called a hash lookup.) The result
might be:
Today's date: 29-09-2003
Hashes can contain hashes, e.g.:
Date of birth:
${user.dob.day}-${user.dob.month}-${user.dob.year}
The string value of any variable (or other expression) can be used as a hash key by
enclosing the expression in square brackets:
Error in field "${fieldName}": ${errors[fieldName]}
If the value of fieldName was always "title", the above would be the same as
writing:
Error in field "title": ${errors.title}
Generated documents often contain lists of data. In a template, the #foreach
statement processes all the elements in a list. Here's a simple example:
<p>Signatories:</p>
<ul>
#foreach (signatory in signatories)
<li>${signatory}</li>
#end
</ul>
The output might look like this:
<p>Signatories:</p>
<ul>
<li>Arthur Artichoke</li>
<li>Bernard Banana</li>
<li>Carol Carrot</li>
<li>Dorothy Date</li>
</ul>
For each element in the list, the #foreach statement stores the element's value
temporarily in the name given before the in, then processes the template text
between the #foreach and the #end.
Here's an example that generates an HTML table:
<table>
<thead>
<tr>
<th>Name</th>
<th>Date of Birth</th>
<th>Favourite Colour</th>
</tr>
</thead>
<tbody>
#foreach (person in garden.people)
<tr>
<td>${person.name}</td>
<td>${person.bdate}</td>
<td>${person.colour}</td>
</tr>
#end
</tbody>
</table>
Here garden is a hash that contains a list called people. Each element of
people is a hash containing three scalars (name, bdate and colour).
2.4 Conditionals
A template can contain optional text, which is used only if some condition is met. The
#if statement tests a condition. For example:
#if (approved)
This document has been approved for publication.
#else
This document is awaiting approval.
#end
We have seen scalars that contain strings (i.e. text); true and false are also
possible values of a scalar (e.g. approved above). Any scalar is equal to true
if it has a value other than 0 or the empty string. A list is equal to true if it
exists and isn't empty. A hash is equal to true if it exists. This makes it
convenient to check, for example, whether a list contains any values before processing its
contents:
#if (searchResults)
#foreach (result in searchResults)
...
#end
#end
If a scalar contains a string or a number, an expression can test the scalar's value,
using comparison operators such as == (equals), != (is unequal to), <
(is less than) and > (is greater than). You can also use #elseif blocks to
test several conditions. For example:
#if (hour > 17)
Good evening!
#elseif (hour > 12)
Good afternoon!
#else
Good morning!
#end
#if (status == "approved")
This document has been approved for publication.
#else
This document is awaiting approval.
#end
See Section 3.6 for the full details of expressions.
2.5 Setting values
The #set statement assigns a value to a name. The value is not set in the data
model that the program has provided; a template cannot use #set to change its data
model. The value remains internal to the template, and only while the template is being
merged; it is then forgotten. Returning to the earlier example of an HTML table, suppose
we wanted the background colour of the rows to alternate between yellow and white. We
could write:
<tbody>
#set (background = "white")
#foreach (person in garden.people)
<tr bgcolor="${background}">
<td>${person.name}</td>
<td>${person.bdate}</td>
<td>${person.colour}</td>
</tr>
#if (background == "white")
#set (background = "yellow")
#else
#set (background = "white")
#end
#end
</tbody>
2.6 Including Templates in Templates
Rather than copy and paste the same text into several templates, you can put the common
text in a separate template, and include that template in other templates using the #include statement. For example, you might include a standard header and footer on
each page:
#include ("header.tmpl")
...
#include ("footer.tmpl")
Included templates will not see any values that have been set in the including template,
nor can the including template see any values that the included template sets. If you
want to pass values into a reusable section of template code, use a macro, as described in
the next section.
2.7 Macros
To create a reusable bit of template code that uses values you provide, you can write a macro. The #macro statement defines a macro, which can then be used as a
statement in its own right. For example, here is a macro that formats a date in a
particular way, given the year, month and day as numbers:
#macro formatDate(year, month, day)
#var (monthPrefix)
#var (dayPrefix)
#if (month < 10)
#set (monthPrefix = "0")
#end
#if (day < 10)
#set (dayPrefix = "0")
#end
${year}-${monthPrefix}${month}-${dayPrefix}${day}
#end
(The #var statement will be explained in a moment.)
Here is some template code that expects a hash called date like the one we saw in
Section 2.2, and uses it to call the formatDate macro above:
Today's date:
#formatDate(date.year, date.month, date.day)
A macro may be called with fewer arguments than it was defined with; the remaining
arguments are set to null. It is an error to call a macro with too many arguments.
2.7.1 Defining Variables in Macros
The #var statement in the macro above initialises a variable for use within the
macro, setting it to a null value. We could have written:
#set (monthPrefix = "")
But if there was already a variable called monthPrefix outside the macro, #set would change the value of the existing variable. (Sometimes this might be what you
want.) By contrast, a variable initialised inside a macro with #var only exists
within that macro, and doesn't affect any other variable that might have the same name
outside the macro; its value is forgotten once the macro has completed. Once you have
used #var to initialise a variable in a macro, you can use #set to change
its value, as in the example above. To initialise a variable with a value other than
null, you can write:
#var (colour = "blue")
When used outside of a macro, #var has the same effect as #set.
2.7.2 Storing Macros in Separate Templates
If there are some macros that you want to use in more than one template, you can define
them in a separate template, which we'll call a macro template. In each template
where you want to use those macros, you then need to tell CamlTemplate where to look for
them, using the #open statement. For example, if you've written a macro template
called macros.tmpl, and you want to use them in a template called test.tmpl,
you would put the following line in test.tmpl, before using any of the macros:
#open ("macros.tmpl")
You can put several #open statements in a template. When you call a macro,
CamlTemplate looks for it first in the template that's being merged, and then in any macro
templates that have been opened in that template.
2.8 Functions
A function can be supplied to a template as part of its data. Since functions are written
in Objective Caml, they can do things that would be cumbersome or impossible to do in
macros. A function takes one or more expressions as arguments, and returns a value, which
can be used in an expansion or in a statement.
For example, CamlTemplate provides a function called escHtml, for escaping special
characters in HTML documents. It can be used like this:
Company name: ${escHtml(companyName)}
If the value of companyName was Artichoke & Banana, the output would be:
Company name: Artichoke & Banana
In addition to escHtml, CamlTemplate provides the following functions, which
application developers can choose to make available to templates:
-
urlEncode
- URL-encodes a string.
- escHtmlAttr
- Escapes special characters in text to be included in an HTML attribute.
- escHtmlTextarea
- Escapes special characters in text to be included in an HTML textarea.
- asList
- Converts any value to a list, if it isn't already a list. If the argument is
a list, returns the argument. If the argument is null, returns an empty list. Otherwise,
returns a single-element list containing the argument. This is useful for dealing with
form input fields that can have multiple values.
Each of these functions expects one argument.
2.9 Comments
You can write a comment in a template by surrounding it with #* and *#:
#* This is a comment. *#
Comments do not appear in the output when a template is merged. Comments can contain
comments.