Golang HTML Template Partials

2017-09-28

An example of rendering a collection of posts using golang as a web server with it's built in "net/http" and "html/template" packages. In this example we make use of functions in the template and also render one template inside another.

package main

import (
  "html/template"
  "io/ioutil"
  "log"
  "net/http"
  "time"
)

func main() {
  mux := http.NewServeMux()

  server := &http.Server{
    Addr:    ":8080",
    Handler: mux,
  }

  funcs := template.FuncMap{
    "Date": func() string { return time.Now().Format("2006-01-02") },
  }
  layout := initTemplate(template.New("layout"), funcs)
  initTemplate(layout.New("post"), funcs)

  mux.HandleFunc("/", home(layout))

  log.Fatal(server.ListenAndServe())
}

type Post struct {
  Title string
  Body  string
}

func home(layout *template.Template) func(http.ResponseWriter, *http.Request) {
  posts := []Post{
    Post{Title: "Hello World!", Body: "I need to start coming up with something better to say than hello world."},
    Post{Title: "Foo bar", Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent egestas."},
  }
  return func(w http.ResponseWriter, r *http.Request) {
    err := layout.Execute(w, posts)
    if err != nil {
      log.Panic(err)
    }
  }
}

func initTemplate(template *template.Template, funcMap template.FuncMap) *template.Template {
  template.Funcs(funcMap)
  contents, err := ioutil.ReadFile(template.Name() + ".html.tmpl")
  if err != nil {
    log.Panic(err)
  }
  template.Parse(string(contents))
  return template
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>jdc</title>
  </head>
  <body>
    {{ range . }}
      {{ template "post" . }}
    {{ end}}
  </body>
</html>
<section>
  <h1>{{ Date }} - {{ .Title }}</h1>
  <p>{{ .Body }}</p>
</section>

There is a bit more going on here compared to the previous example. The two main upgrades are rendering a template within a template, and calling functions in a template.

  funcs := template.FuncMap{
    "Date": func() string { return time.Now().Format("2006-01-02") },
  }

Templates have a template.FuncMap which is a collection of funcs accessible in the template. We must have the template.FuncMap setup before the template is parsed. In the above code snippet we create a simple template.FuncMap with a single function to return a date string.

  layout := initTemplate(template.New("home"), funcs)

We've extracted our template initialisation code into the func initTemplate. The func is called and passed in a new empty template and the template.FuncMap we made earlier. It returns to us a useableĀ *template.Template which we store in layout.

func initTemplate(template *template.Template, funcMap template.FuncMap) *template.Template {
  template.Funcs(funcMap)
  contents, err := ioutil.ReadFile(template.Name() + ".html.tmpl")
  if err != nil {
    log.Panic(err)
  }
  template.Parse(string(contents))
  return template
}

We could useĀ template.ParseFiles and it would save us from having to read the template files explicitly with ioutil.ReadFile but it would name our template as "post.html.tmpl" matching the filename. This makes it a little more clumsy when rendering it by name. I'd prefer {{ template "post" . }} over {{ template "post.html.tmpl" . }}.

The reason we create a new template and pass that into initTemplate instead of creating a template in the func is so that we can reuse it to initialise our layout template and also the post template.

  layout := initTemplate(template.New("home"), funcs)
  initTemplate(layout.New("post"), funcs)

The first template is created with template.New("home") and the second with layout.New("post"). Doing it this way allows us to invoke the "post" template in the "layout" template.

type Post struct {
  Title string
  Body  string
}

Create a simple struct for containing our posts.

func home(layout *template.Template) func(http.ResponseWriter, *http.Request) {

Our func (controller action in rails parlance) home takes a pointer to our layout template, and returns the standard HandlerFunc.

  posts := []Post{
    Post{Title: "Hello World!", Body: "I need to start coming up with something better to say than hello world."},
    Post{Title: "Foo bar", Body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent egestas."},
  }

Create a slice of Posts to render.

  return func(w http.ResponseWriter, r *http.Request) {
    err := layout.Execute(w, posts)
    if err != nil {
      log.Panic(err)
    }
  }

Return a HandlerFunc closure which executes our layout template passing in posts. And as always, take care of any errors.

  <body>
    {{ range . }}
      {{ template "post" . }}
    {{ end}}
  </body>

The posts passed into layout.Execute(w, posts) are referenced by '.' in the template, as in {{ range . }}. range loops over the posts, setting dot '.' to each post. We then render the post template passing it each post {{ template "post" . }}.

<section>
  <h1>{{ Date }} - {{ .Title }}</h1>
  <p>{{ .Body }}</p>
</section>

The post template renders each post. {{ Date }} is how our "Date" func in the funcMap is called. {{ .Title }} prints the posts Title field. I'll leave it up to the reader to work out what {{ .Body }} is doing.