Writing Templates

## 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}

### 2.3 Lists

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 &amp; 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.