Go: Sharing templates across multiple pages
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:
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.
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
.