Templates: Using Actions, Functions, and Custom Functions

My previous blog post introduced the feature of actions in templates. Another very similar feature is a function, which can be used together with an action to perform additional operations on the data. For example, {{ range }}
is an action as it contributes to the overall structure of the template, whereas {{ len }}
is a function as it performs a calculation. In this blog post, I will first demo how to use actions and functions together. I will then go over how to use custom functions in templates. We start with the following code in main.go
:
package main
import (
"os"
"text/template"
"strings"
)
type Person struct {
Name string
Age int
Employed bool
Occupation string
Interests []string
}
func main() {
people := []Person{
{
Name: "Bob",
Age: 42,
Employed: true,
Occupation: "Doctor",
Interests: []string{"Reading", "Swimming"},
},
{
Name: "Jessica",
Age: 22,
Employed: true,
Occupation: "Teacher",
Interests: []string{"Piano", "Baking"},
},
{
Name: "Ben",
Age: 23,
Employed: true,
Occupation: "Photographer",
Interests: []string{"Music", "Running"},
},
}
tmplFile := "demo.tmpl"
tmpl, err := template.New(tmplFile).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, people)
if err != nil {
panic(err)
}
}
Note that I added an attribute called Interests
(slice of strings
) to the Person
struct. For an explanation of this code, please read my previous post.
In demo.tmpl
, we can use the slice
function and the range
action to loop over a section of the slice people
.
{{ range slice . 1 2 }}
-----------------
Name: {{.Name}}
Age: {{.Age}}
Occupation: {{ if .Employed }}{{ .Occupation }}{{ else }}Unemployed{{end}}
{{ end }}
{{ slice . 1 2 }}
is equivalent to people[1:2]
, and so calling range
on this slice iterates through the second element of people
and prints out the following string:
-----------------
Name: Jessica
Age: 22
Occupation: Teacher
For the purpose of printing out one element, it may be easier to use the index
function. For example, the line {{ index . 0 }}
is equivalent to people[0]
and would print out {Bob 42 true Doctor [Reading Swimming]}
.
Creating a plaintext report solely through the use of actions and built-in functions in templates can be quite restrictive as arithmetic operations cannot be used in templates directly.
Introducing the option to feed in custom functions to your templates. These functions can be defined however you want, and they give you the possibility to perform operations that are otherwise not allowed in templates.
Let’s say you want to index the last element of the slice you pass in. If you know the length of the slice, then you can simply hardcode the index number. But to make this more reusable, we need to pass in a custom function that allows us to decrement the length by one so that we can index the last element of any slice.
We start by defining a new function called sub
in main.go
:
func sub(i int) int {
return i - 1
}
In the main
function, we create a map fMap
that maps a function name (string
) to the function definition. We then pass this into the template through the template.New().Funcs
function:
fMap := template.FuncMap{
"sub": sub,
}
tmpl, err := template.New(tmplFile).Funcs(fMap).ParseFiles(tmplFile)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, people)
if err != nil {
panic(err)
}
In the template file, we write {{ len . | sub | index . }}
, indicating that the length of the slice is passed to the sub
function, and the output of the sub
function is passed to the index
function. Therefore, when passing in the slice people
, the output of this line is people[2]
or { Ben 23 true Photographer [Music Running]}
.
In a similar way, we can pass built-in Go functions into a template. For example, let’s say we want to convert the slice of Interests
into a string where each interest is separated by &
. To do this, we first pass in the function strings.Join
to the template by adding a key-value pair to fMap
like so:
fMap := template.FuncMap{
"sub": sub,
"join": strings.Join,
}
Going back to demo.tmpl
, this time looping through the whole slice, we can use this function to return the desired string:
{{ range . }}
-----------------
Name: {{.Name}}
Age: {{.Age}}
Occupation: {{ if .Employed }}{{ .Occupation }}{{ else }}Unemployed{{end}}
Interests: {{ join .Interests " & " }}
{{ end }}
Saving demo.tmpl
and running go run main.go
gives the following result:
-----------------
Name: Bob
Age: 42
Occupation: Doctor
Interests: Reading & Swimming
-----------------
Name: Jessica
Age: 22
Occupation: Teacher
Interests: Piano & Baking
-----------------
Name: Ben
Age: 23
Occupation: Photographer
Interests: Music & Running
Subscribe to my newsletter
Read articles from Alissa Lozhkin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Alissa Lozhkin
Alissa Lozhkin
My name is Alissa and I am a fourth-year computer science and math student at the University of Toronto! I am very interested in computer science and software development, and I love learning about new technologies! Get in touch with me on LinkedIn: https://www.linkedin.com/in/alissa-lozhkin