Go templates
Go’s standard library provides a templating engine that to some is, at least initially, more obtuse than other templating engines, such as Jinja and that of Django, despite its more minimalistic design. However, once understood, Go’s templating engine is sufficient for most purposes and can be extended if needed. In this article, I provide alternate and supplemental explanations to some topics covered in the documentation of the “text/template” package.
Defining
A template is either defined or undefined. A defined template has a definition, whereas an undefined template does not.
An undefined template is created by calling method func New and providing a name.
go
t := template.New("mytemplatename")An undefined template becomes defined by parsing a body. If the body is a string, you may call method func (*Template) Parse.
go
t := template.New("mytemplatename")
var err error
t, err = t.Parse("this is my body")Use func Must to wrap a call returning (*Template, error) if you wish to panic when the error is non-nil.
go
t := template.New("mytemplatename")
t = template.Must(t.Parse("this is my body"))Associating
Each template is associated with one or more templates. A template is associated with itself. The relation is associated is transitive, which means that if template A is associated with template B and B is associated with template C, then A is associated with C. The relation is associated defines a set of associated templates I’ll refer to as an association set. 1
Implementation-wise, each template indirectly has a variable tmpl of type map[string]*Template, where the keys and values represent template names and references, respectively.
go
type common struct {
// ...
tmpl map[string]*Template
// ...
}
type Template struct {
// ...
*common
// ...
}Templates belonging to the same association set have the same tmpl variable value. However, tmpl does not have name-reference pairs for undefined templates. It has name-reference pairs only for defined templates.
Retrieve the defined templates associated with a template by calling method func (*Template) Templates. Retrieve only the names of those defined templates by calling method func (*Template) DefinedTemplates.
An association set is created by calling one of the following functions:
Add more templates to the association set of a template by calling one of the following methods:
func (*Template) Newfunc (*Template) ParseFSfunc (*Template) ParseFilesfunc (*Template) ParseGlob
Here’s a simple example where templates “T1” and “T2” belong to the same association set:
go
// An association set is created.
t1 := template.New("T1")
// "T2" is added to the association set to which "T1" belongs.
t2 := t1.New("T2")Nesting
A template body may contain definitions of other templates prior to parsing. When a body is parsed, definitions are extracted and then used to construct templates that are added to the same association set as the template to whom the body corresponds. Definitions must be at the top level of a body.
In the example below, “T1”, “T2”, and “T3” belong to the same association set. The resulting definition of “T1” after parsing is “B1”. “T2” and “T3” have the definitions “B2” and “B3”, respectively.
go
t1 := template.New("T1")
body := `B1{{define "T2"}}B2{{end}}{{define "T3"}}B3{{end}}`
t1 = template.Must(t1.Parse(body))Executing
Template execution applies a template to a data structure. Execute a template by either using the “template” action in a body or calling one of the following methods:
Call func (*Template) Execute to execute the receiver template.
go
t1 := template.New("T1")
body := `B1{{define "T2"}}B2{{end}}{{define "T3"}}B3{{end}}`
t1 = template.Must(t1.Parse(body))
// Execute "T1" and write "B1" to standard output.
err := t1.Execute(os.Stdout, nil)Call func (*Template) ExecuteTemplate to execute the template identified by the provided name that’s in the same association set as the template receiver.
go
t1 := template.New("T1")
body := `B1{{define "T2"}}B2{{end}}{{define "T3"}}B3{{end}}`
t1 = template.Must(t1.Parse(body))
// Execute "T2" and write "B2" to standard output.
err := t1.ExecuteTemplate(os.Stdout, "T2", nil)
An alternative to func (*Template) ExecuteTemplate would be to retrieve “T2” by calling func (*Template) Lookup and then func (*Template) Execute to execute “T2”.
go
t1 := template.New("T1")
body := `B1{{define "T2"}}B2{{end}}{{define "T3"}}B3{{end}}`
t1 = template.Must(t1.Parse(body))
t2 := t1.Lookup("T2")
err := t2.Execute(os.Stdout, nil)Substituting
A useful pattern is to define a base template that uses the “template” or “block” action to execute another template. In this section we are using the “html/template” package, which has the same interface as the “text/template” package and protects the HTML output from certain attacks.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))If you execute the base template as it is, you’ll get an error about template “layout” being missing.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))
if err := baseTmpl.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}stderr
html/template:base:8:15: no such template "layout"You can define the “layout” template in several ways. The first way is to define the template using the “define” action in a body and then parse the body from the base template.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))
layoutTmplBody := `{{define "layout"}}I AM LAYOUT{{end}}`
baseTmpl = template.Must(baseTmpl.Parse(layoutTmplBody))
if err := baseTmpl.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}stdout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My awesome title</title>
</head>
<body>
I AM LAYOUT
</body>
</html>Note that if the “layout” template body had text content, then the base template definition would have been overwritten by that text.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))
layoutTmplBody := `HELLO{{define "layout"}}I AM LAYOUT{{end}}WORLD`
baseTmpl = template.Must(baseTmpl.Parse(layoutTmplBody))
if err := baseTmpl.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}stdout
HELLOWORLDThe other way to define the “layout” template would be to create an undefined template and then parse the template body without using the {{define "layout"}}-{{end}} pair.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))
layoutTmplBody := "I AM LAYOUT"
// Reference to layout template is not needed in this scope,
// so the returned reference is not saved.
template.Must(baseTmpl.New("layout").Parse(layoutTmplBody))
if err := baseTmpl.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}stdout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My awesome title</title>
</head>
<body>
I AM LAYOUT
</body>
</html>If you want to render two pages with different layouts, you can, for each page, use func (*Template) Clone to clone the base template and then parse the layout body corresponding to the page.
go
baseTmplBody := `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{.title}}</title>
</head>
<body>
{{template "layout"}}
</body>
</html>
`
data := map[string]string{"title": "My awesome title"}
baseTmpl := template.Must(template.New("base").Parse(baseTmplBody))
bt1 := template.Must(baseTmpl.Clone())
bt2 := template.Must(baseTmpl.Clone())
l1 := `{{define "layout"}}L1{{end}}`
l2 := `{{define "layout"}}L2{{end}}`
bt1 = template.Must(bt1.Parse(l1))
bt2 = template.Must(bt2.Parse(l2))
if err := bt1.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
}
fmt.Fprint(os.Stdout, "\n")
if err := bt2.Execute(os.Stdout, data); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}stdout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My awesome title</title>
</head>
<body>
L1
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My awesome title</title>
</head>
<body>
L2
</body>
</html>Footnotes
-
The is associated relation is a binary relation because it is a relation from a set of templates A to a set of templates B. Because sets A and B happen to be the same set, the relation is said to be on the set A. ↩︎