Go: Sharing templates across multiple pages

A nice way to inherit between template collections in html/template
Renaissance gopher Johannes Gopherberg after inventing html/template

Renaissance gopher Johannes Gopherberg after inventing html/template

I’m using html/template much too seldom to remember how to properly share common template blocks across multiple pages. Today I had to figure it out for the second time. It’s about time to document it!

The approach presented here both (1) makes it possible to share template blocks across multiple logical pages, and (2) makes it easy to use them using tmpl.Execute(writer, data) in your HTTP handlers.

In particular, with this approach, all pages share a common top-level definition of an HTML page, as opposed to maintaining separate header and footer templates whose opening and closing tags would need to be kept in sync.

From the text/template documentation:

Clone can be used to prepare common templates and use them with variant definitions for other templates by adding the variants after the clone is made.

With Clone(), you can have a separate template.Template instance for each of your pages, while making them share a common set of base templates at the same time.

The way you’d like to construct these goes something like this:

Constructing separate template collections from a shared base

Constructing separate template collections from a shared base

None of this is really a secret. However, before .Clone() existed, it wasn’t as easy, and it’s still easy to find a lot of outdated advice.

Example

Let’s go step by step.

Directory structure for this example

The base templates live in templates/base/*.html. These include page.html for the top-level definition of an HTML file, a dummy definition for the main page content in main.html, as well as various helpers that may be reused across various pages.

The per-page templates live in templates/${PAGENAME}/*.html. A minimal page just redefines main.html to have the necessary content.

The templates in foo/ override main.html from base/.

The templates in foo/ override main.html from base/.

Create the base templates

First, define the base templates with the page.html definition as root template for all template instantiations.

base := template.New("page.html")
base  = template.Must(
    base.ParseGlob("templates/base/*.html")))

We name the template collection page.html, so that page.html will be used as the default template to render for all of our pages, when it gets called through .Execute().

The templates/base/page.html template defines the top-level HTML structure with navigation bars and core site elements, and includes the main page content main.html with a block action:

<html>
<head><title>...</title></head>
<body>
  <!-- navigation, main site elements, etc -->
  {{- block "main.html" . -}}{{- end -}}
</body>
</html>

Derive the specialized templates for the foo handler

In the package or file for the foo handler, create a local var fooTmpl *template.Template where you derive the base template:

fooTmpl := template.Must(baseTmpl.Clone())
fooTmpl  = template.Must(
    fooTmpl.ParseGlob("templates/foo/*.html"))

We define page content in the templates/foo/main.html template:

<h1>Hello, world!</h1>
<p>This is an example template.</p>

Use the specialized templates in your handler

Finally, all that’s needed to use the specialized templates in a handler is to just call .Execute():

func handleFoo(w http.ResponseWriter, req *http.Request) {
        data := fooData{Key: "Value"}
        fooTmpl.Execute(w, data)
}

It will automatically instantiate the collection of templates starting from page.html.

Comments