Go - Templating
Go templating library is for data-driven textual output. Most commonly used to create HTML.
All examples work on Go Playground.
Verbose usage:
package main
import (
"bytes"
"fmt"
"html/template"
)
func main() {
// Template definition as a string.
// Note the `.`, you can think of it as `this` or `self` in various
// languages and at the root level it's the `data` passed to Execute.
content := "Hello {{ .Name }}!"
// Variable data fed to the template.
data := struct{ Name string }{Name: "Ruksi"}
// template.New creates an undefined template.
// *Template.Parse defines the template.
// template.Must is just a helper that panics if it received error.
t := template.Must(template.New("greeting").Parse(content))
// Execute compiles the template to a writer because strings are immutable
// and compilation happens in a series of steps. The writer is commonly
// `http.ResponseWriter` but here is how it's saved to a string:
buff := new(bytes.Buffer)
err := t.Execute(buff, data)
if err != nil {
panic(err)
}
fmt.Println(buff.String())
// => Hello Ruksi!
}
You can define in-template helpers using Funcs
.
package main
import (
"html/template"
"os"
)
func main() {
funcMap := template.FuncMap{
"concat": func(x string, y string) string { return x + y },
}
content := `{{ concat "11" "22" }}!`
t := template.Must(template.New("addition").Funcs(funcMap).Parse(content))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
// => 1122!
}
Variables are defined with $variable
.
package main
import (
"html/template"
"os"
)
func main() {
content := `{{ $localVar := "Goodbye" }}{{ $localVar }} World!`
t := template.Must(template.New("addition").Parse(content))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
// => Goodbye World!
}
Collection are iterated with range
.
package main
import (
"html/template"
"os"
)
func main() {
// In reality, these templates are read from file system.
content := `
<ul>
{{ range $index, $name := .Names }}
<li>{{ $name }} ({{ $index }})</li>
{{ end }}
</ul>`
data := struct{ Names []string }{Names: []string{"Ruksi", "Tommi", "Tuomas"}}
t := template.Must(template.New("iteration").Parse(content))
if err := t.Execute(os.Stdout, data); err != nil {
panic(err)
}
// => <ul><li>Ruksi (0)</li>... but with a lot of whitespace.
}
You can get back to the global scope with $.
.
{{ $.Site.Title }}
Basic conditions work like you would expect. if
, else
, and
, or
.
package main
import (
"html/template"
"os"
)
func main() {
content := "Hello {{ if .Name }}{{ .Name }}{{ end }}!"
data := struct{ Name string }{Name: "Ruksi"}
t := template.Must(template.New("addition").Parse(content))
err := t.Execute(os.Stdout, data)
if err != nil {
panic(err)
}
// => Hello Ruksi!
}
with
can be used to switch context. Also works as an if
, generating no output if the value doens't exist.
package main
import (
"html/template"
"os"
)
func main() {
content := "Hello {{ with .Name }}{{ . }}{{ end }}!"
data := struct{ Name string }{Name: "Ruksi"}
t := template.Must(template.New("addition").Parse(content))
err := t.Execute(os.Stdout, data)
if err != nil {
panic(err)
}
// => Hello Ruksi!
}
Partial views can be done with template
in-template function. template
definitions must be defined in templates.
package main
import (
"html/template"
"os"
)
func main() {
layoutContent := `<body>{{ template "body" . }}</body>`
layoutTemplate, errParseLayout := template.New("layout.html").Parse(layoutContent)
if errParseLayout != nil {
panic(errParseLayout)
}
viewContent := `{{ define "body" }}Hello World!{{ end }}`
_, errParseView := layoutTemplate.New("view.html").Parse(viewContent)
if errParseView != nil {
panic(errParseView)
}
errExecute := layoutTemplate.Execute(os.Stdout, nil)
if errExecute != nil {
panic(errExecute)
}
// => <body>Hello World!</body>
}
block
can be used for partial templates that are optional.
package main
import (
"html/template"
"os"
)
func main() {
layoutContent := `<body>{{block "body" .}}Hello Default!{{end}}</body>`
layoutTemplate, errParseLayout := template.New("layout.html").Parse(layoutContent)
if errParseLayout != nil {
panic(errParseLayout)
}
errExecute := layoutTemplate.Execute(os.Stdout, nil)
if errExecute != nil {
panic(errExecute)
}
// => <body>Hello Default!</body>
}
Pipe operator (|
) works how would expect. Basically routes output from left side to input of right side.
{{ if eq 1 1 }} Same {{ end }} // no pipe
{{ eq 1 1 | if }} Same {{ end }} // pipe
{{ index .Params "disqus_url" | urlquery }}
{{ index .Params "disqus_url" | html }}
{{ index .Params "disqus_url" | js }}
You can write comments to templates that will not be rendered. They can be multi-line.
{{/* a comment */}}
You can parse everything in a directory with ParseGlob
.
package main
import (
"html/template"
"os"
"path/filepath"
)
func main() {
// Directory can have any number of templates,
// but the first match becomes
// the starting template.
dir := "/path/to/dir"
pattern := filepath.Join(dir, "*.tmpl")
t := template.Must(template.ParseGlob(pattern))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
Cache your templates if they don't change runtime. Templates may be executed safely in parallel after parsing.