Golang Template Inheritance (Python Jinja2 extends & include)


Introduction

This posts will show how to do template inheritance via Go official html/template package. There are other template engines in Go, such as jigo, pongo2, or mustache, but I like to use official packages as much as possible.

We will introduce template inheritance by three steps:

  1. Define a base skeleton template that contains all the common elements of your site and defines blocks that child templates can override.
  2. include another templates in the base skeleton template.
  3. Define a child template that extends the base skeleton template and output the final HTML we need.

Template Hierarchy

Assume we have 4 templates as follows:

tmpl/index.html
tmpl/layout/layout.html
tmpl/layout/includes/metaog.html
tmpl/layout/includes/footer.html
  • layout.html is the base template (skeleton)
  • metaog.html and footer.html will be included in layout.html
  • index.html is the child template that extends layout.html

Look at layout.html (base skeleton template) first:

layout.html | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
{{define "layout"}}
<!doctype html>
<html prefix="og: http://ogp.me/ns#">
<head>
  <meta charset="utf-8">
  <title>{{.SITENAME}}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  {{template "metaog" .}}
</head>
<body>

{{block "content" .}}{{end}}

{{template "footer" .}}
</body>
</html>
{{end}}

In line 8 and 14 of layout.html, two templates(metaog and footer) are included via template action, which is roughly the same as the Jinja2 include tag.

In line 12 of layout.html, a block named content is declared and will later be overridden by child template. The block here is also roughly the same as the Jinja2 blocks.

Next, look at the two tempaltes included in base skeleton:

metaog.html | repository | view raw
1
2
3
4
{{define "metaog"}}
  <meta property="og:title" content="{{.SITENAME}}">
  <meta property="og:url" content="{{.SITEURL}}">
{{end}}
footer.html | repository | view raw
1
2
3
4
5
{{define "footer"}}
<div>Powered by
  <a href="https://golang.org/">Go</a>
</div>
{{end}}

Nothing special above. The syntax is quite intuitive and easy to understand.

Finally, we will see how the child template (index.html) extends the base template (layout.html) and override the content block in the base template:

index.html | repository | view raw
1
2
3
4
5
{{template "layout" .}}
{{define "content"}}
<div>Hello World!</div>
<div>Template Inheritance in Go html/template</div>
{{end}}

The effect of template action in the first line is the same as the Jinja2 extends tag, and from line 2 to last line, the content block is defined and override the declaration in the base template.

Template Rendering

The following ParseTemplateDir function reads all the above 4 templates under tmpl directory: [5]

template.go | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package gossg

import (
	"html/template"
	"os"
	"path/filepath"
)

func ParseTemplateDir(dir string) (*template.Template, error) {
	var paths []string
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			paths = append(paths, path)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return template.ParseFiles(paths...)
}

Define the template data and render index.html template to generate final HTML output:

template_test.go | repository | view raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package gossg

import (
	"os"
	"testing"
)

type TemplateData struct {
	SITENAME string
	SITEURL  string
}

func TestTemplateToHtml(t *testing.T) {
	data := TemplateData{
		SITENAME: "Theory and Practice",
		SITEURL:  "https://siongui.github.io/",
	}

	tmpl, err := ParseTemplateDir("tmpl")
	if err != nil {
		t.Error(err)
	}
	err = tmpl.ExecuteTemplate(os.Stdout, "index.html", &data)
	if err != nil {
		t.Error(err)
	}
}

Final HTML output:

=== RUN   TestTemplateToHtml

<!doctype html>
<html prefix="og: http://ogp.me/ns#">
<head>
  <meta charset="utf-8">
  <title>Theory and Practice</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <meta property="og:title" content="Theory and Practice">
  <meta property="og:url" content="https://siongui.github.io/">

</head>
<body>


<div>Hello World!</div>
<div>Template Inheritance in Go html/template</div>



<div>Powered by
  <a href="https://golang.org/">Go</a>
</div>

</body>
</html>


--- PASS: TestTemplateToHtml (0.00s)
PASS

Tested on:

  • Ubuntu Linux 16.10
  • Go 1.7.5

References:

[1]Golang html/template versus Python Jinja2 (6) - Template Inheritance (Extends)
[2][Golang] Example for block Action in Template package
[3]

golang arguments dot - Google search

golang arguments dot - DuckDuckGo search

golang arguments dot - Bing search

golang arguments dot - Yahoo search

golang arguments dot - Baidu search

golang arguments dot - Yandex search

[4][Golang] Walk All Files in Directory
[5]Golang Template Parse Directory
[6]Creating a simple website, how to use the out of the box library "html/template" to create a layout and its children? : golang