ruk·si

🐹 Go
Templating

Updated at 2016-12-10 11:44

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.

Sources