Template actions and functions
In this section we’re going to look at the template actions and functions that Go provides.
We’ve already talked about some of the actions — {{define}}
, {{template}}
and {{block}}
— but there are three more which you can use to control the display of dynamic data — {{if}}
, {{with}}
and {{range}}
.
Action | Description |
---|---|
{{if .Foo}} C1 {{else}} C2 {{end}} |
If .Foo is not empty then render the content C1, otherwise render the content C2. |
{{with .Foo}} C1 {{else}} C2 {{end}} |
If .Foo is not empty, then set dot to the value of .Foo and render the content C1, otherwise render the content C2. |
{{range .Foo}} C1 {{else}} C2 {{end}} |
If the length of .Foo is greater than zero then loop over each element, setting dot to the value of each element and rendering the content C1. If the length of .Foo is zero then render the content C2. The underlying type of .Foo must be an array, slice, map, or channel. |
There are a few things about these actions to point out:
For all three actions the
{{else}}
clause is optional. For instance, you can write{{if .Foo}} C1 {{end}}
if there’s noC2
content that you want to render.The empty values are false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero.
It’s important to grasp that the
with
andrange
actions change the value of dot. Once you start using them, what dot represents can be different depending on where you are in the template and what you’re doing.
The html/template
package also provides some template functions which you can use to add extra logic to your templates and control what is rendered at runtime. You can find a complete listing of functions here, but the most important ones are:
Function | Description |
---|---|
{{eq .Foo .Bar}} |
Yields true if .Foo is equal to .Bar |
{{ne .Foo .Bar}} |
Yields true if .Foo is not equal to .Bar |
{{not .Foo}} |
Yields the boolean negation of .Foo |
{{or .Foo .Bar}} |
Yields .Foo if .Foo is not empty; otherwise yields .Bar |
{{index .Foo i}} |
Yields the value of .Foo at index i . The underlying type of .Foo must be a map, slice or array, and i must be an integer value. |
{{printf "%s-%s" .Foo .Bar}} |
Yields a formatted string containing the .Foo and .Bar values. Works in the same way as fmt.Sprintf(). |
{{len .Foo}} |
Yields the length of .Foo as an integer. |
{{$bar := len .Foo}} |
Assign the length of .Foo to the template variable $bar |
The final row is an example of declaring a template variable. Template variables are particularly useful if you want to store the result from a function and use it in multiple places in your template. Variable names must be prefixed by a dollar sign and can contain alphanumeric characters only.
Using the with action
A good opportunity to use the {{with}}
action is the view.tmpl
file that we created in the previous chapter. Go ahead and update it like so:
{{define "title"}}Snippet #{{.Snippet.ID}}{{end}} {{define "main"}} {{with .Snippet}} <div class='snippet'> <div class='metadata'> <strong>{{.Title}}</strong> <span>#{{.ID}}</span> </div> <pre><code>{{.Content}}</code></pre> <div class='metadata'> <time>Created: {{.Created}}</time> <time>Expires: {{.Expires}}</time> </div> </div> {{end}} {{end}}
So now, between {{with .Snippet}}
and the corresponding {{end}}
tag, the value of dot is set to .Snippet
. Dot essentially becomes the models.Snippet
struct instead of the parent templateData
struct.
Using the if and range actions
Let’s also use the {{if}}
and {{range}}
actions in a concrete example and update our homepage to display a table of the latest snippets, a bit like this:
First update the templateData
struct so that it contains a Snippets
field for holding a slice of snippets, like so:
package main import "snippetbox.alexedwards.net/internal/models" // Include a Snippets field in the templateData struct. type templateData struct { Snippet models.Snippet Snippets []models.Snippet }
Then update the home
handler function so that it fetches the latest snippets from our database model and passes them to the home.tmpl
template:
package main ... func (app *application) home(w http.ResponseWriter, r *http.Request) { w.Header().Add("Server", "Go") snippets, err := app.snippets.Latest() if err != nil { app.serverError(w, r, err) return } files := []string{ "./ui/html/base.tmpl", "./ui/html/partials/nav.tmpl", "./ui/html/pages/home.tmpl", } ts, err := template.ParseFiles(files...) if err != nil { app.serverError(w, r, err) return } // Create an instance of a templateData struct holding the slice of // snippets. data := templateData{ Snippets: snippets, } // Pass in the templateData struct when executing the template. err = ts.ExecuteTemplate(w, "base", data) if err != nil { app.serverError(w, r, err) } } ...
Now let’s head over to the ui/html/pages/home.tmpl
file and update it to display these snippets in a table using the {{if}}
and {{range}}
actions. Specifically:
We want to use the
{{if}}
action to check whether the slice of snippets is empty or not. If it’s empty, we want to display a"There's nothing to see here yet!
message. Otherwise, we want to render a table containing the snippet information.We want to use the
{{range}}
action to iterate over all snippets in the slice, rendering the contents of each snippet in a table row.
Here’s the markup:
{{define "title"}}Home{{end}} {{define "main"}} <h2>Latest Snippets</h2> {{if .Snippets}} <table> <tr> <th>Title</th> <th>Created</th> <th>ID</th> </tr> {{range .Snippets}} <tr> <td><a href='/snippet/view/{{.ID}}'>{{.Title}}</a></td> <td>{{.Created}}</td> <td>#{{.ID}}</td> </tr> {{end}} </table> {{else}} <p>There's nothing to see here... yet!</p> {{end}} {{end}}
Make sure all your files are saved, restart the application and visit http://localhost:4000
in a web browser. If everything has gone to plan, you should see a page which looks a bit like this:

Additional information
Combining functions
It’s possible to combine multiple functions in your template tags, using the parentheses ()
to surround the functions and their arguments as necessary.
For example, the following tag will render the content C1
if the length of Foo
is greater than 99:
{{if (gt (len .Foo) 99)}} C1 {{end}}
Or as another example, the following tag will render the content C1
if .Foo
equals 1 and .Bar
is less than or equal to 20:
{{if (and (eq .Foo 1) (le .Bar 20))}} C1 {{end}}
Controlling loop behavior
Within a {{range}}
action you can use the {{break}}
command to end the loop early, and {{continue}}
to immediately start the next loop iteration.
{{range .Foo}} // Skip this iteration if the .ID value equals 99. {{if eq .ID 99}} {{continue}} {{end}} // ... {{end}}
{{range .Foo}} // End the loop if the .ID value equals 99. {{if eq .ID 99}} {{break}} {{end}} // ... {{end}}