You're reading a sample of this book. Grab the full version here.

2.8. HTML Templating and Inheritance

Let's inject a bit of life into the project and develop a proper home page for our Snippetbox web application. Over the next couple of chapters we'll work towards creating a page which looks like this:

02.08-01.png

To do this, let's first create a new template file in the ui/html directory...

$ cd $HOME/code/snippetbox
$ touch ui/html/home.page.tmpl

...and then add the following HTML markup for the home page to it:

File: ui/html/home.page.tmpl
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>Home - Snippetbox</title>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        <nav>
            <a href="/">Home</a>
        </nav>
        <section>
            <h2>Latest Snippets</h2>
            <p>There's nothing to see here yet!</p>
        </section>
    </body>
</html>

A quick note on naming conventions: Throughout this book we'll use the convention <name>.<role>.tmpl for naming template files, where <role> is either page, partial or layout. Being able to distinguish the role of the template will help us when it comes to creating a cache of templates later in the book — but actually it doesn't matter so much what naming convention or file extension you use. Go is flexible about this.

So now that we've created a template file with the HTML markup for the home page, the next question is how do we get our home handler to render it?

For this we need to import Go's html/template package, which provides a family of functions for safely parsing and rendering HTML templates. We can use the functions in this package to parse the template file and then execute the template.

I'll demonstrate. Open the cmd/web/handlers.go file and add the following code:

File: cmd/web/handlers.go
package main

import (
    "fmt"
    "html/template" // New import
    "log"           // New import
    "net/http"
    "strconv"
)

func home(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    // Use the template.ParseFiles() function to read the template file into a
    // template set. If there's an error, we log the detailed error message and use
    // the http.Error() function to send a generic 500 Internal Server Error
    // response to the user.
    ts, err := template.ParseFiles("./ui/html/home.page.tmpl")
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
        return
    }

    // We then use the Execute() method on the template set to write the template
    // content as the response body. The last parameter to Execute() represents any
    // dynamic data that we want to pass in, which for now we'll leave as nil.
    err = ts.Execute(w, nil)
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
    }
}

...

It's important to point out that the file path that you pass to the template.ParseFiles() function must either be relative to your current working directory, or an absolute path. In the code above I’ve made the path relative to the root of the project directory.

So, with that said, make sure you're in the root of your project directory and restart the application:

$ cd $HOME/code/snippetbox
$ go run cmd/web/*
2018/08/02 17:49:24 Starting server on :4000

Then open http://localhost:4000/ in your web browser. You should find that the HTML homepage is shaping up nicely.

02.08-02.png

Template Composition

As we add more pages to this web application there will be some shared, boilerplate, HTML markup that we want to include on every page — like the header, navigation and metadata inside the <head> HTML element.

To keep our HTML markup DRY and save us typing, it's a good idea to create a layout (or master) template which contains this shared content, which we can then compose with the page-specific markup for the individual pages.

Go ahead and create a new ui/html/base.layout.tmpl file...

$ touch ui/html/base.layout.tmpl

And add the following markup (which we want to appear on every page):

File: ui/html/base.layout.html
{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "title" .}} - Snippetbox</title>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        <nav>
            <a href='/'>Home</a>
        </nav>
        <section>
            {{template "body" .}}
        </section>
    </body>
</html>
{{end}}

Hopefully this feels familiar if you've used templates in other languages before. It's essentially just regular HTML with some extra actions in double curly braces.

Here we're using the {{define "base"}}...{{end}} action to define a distinct named template called base, which contains the content we want to appear on every page.

Inside this we use the {{template "title" .}} and {{template "body" .}} actions to denote that we want to invoke other named templates (called title and body) at a particular point in the HTML.

Note: If you're wondering, the dot at the end of the {{template "title" .}} action represents any dynamic data that you want to pass to the invoked template. We'll talk more about this later in the book.

Now let's go back to the ui/html/home.page.html and update it to define title and body named templates containing the specific content for the home page:

File: ui/html/home.page.html
{{template "base" .}}

{{define "title"}}Home{{end}}

{{define "body"}}
<h2>Latest Snippets</h2>
<p>There's nothing to see here yet!</p>
{{end}}

Right at the top of this file is arguably the most important part — the {{template "base" .}} action. This informs Go that when the home.page.tmpl file is executed, that we want to invoke the named template base.

In turn, the base template contains instructions to invoke the title and body named templates. I know this might feel a bit circular to start with, but stick with me... in practice this pattern works really quite well.

Once that's done, the next step is to update the code in your home handler so that it parses both template files, like so:

File: cmd/web/handlers.go
package main

...

func home(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    // Initialize a slice containing the paths to the two files. Note that the
    // home.page.tmpl file must be the *first* file in the slice.
    files := []string{
        "./ui/html/home.page.tmpl",
        "./ui/html/base.layout.tmpl",
    }

    // Use the template.ParseFiles() function to read the files and store the
    // templates in a template set. Notice that we can pass the slice of file paths
    // as a variadic parameter?
    ts, err := template.ParseFiles(files...)
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
        return
    }

    err = ts.Execute(w, nil)
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
    }
}

...

So now, instead of containing HTML directly, our template set contains 3 named templates (base, title and body) and an instruction to invoke the base template (which in turn embeds the other two templates).

Feel free to restart the server and give this a try. You should find that it renders the same output as before (although there will be some extra whitespace in the HTML source where the actions are).

The big benefit of using this pattern to compose templates is that you're able to cleanly define the page-specific content in individual files on disk, and within those files also control which layout template the page uses. This is particularly helpful for larger applications, where different pages of your application may need to use different layouts.

Embedding Partials

For some applications you might want to break out certain bits of HTML into partials that can be reused in different pages or layouts. To illustrate, let's create a partial containing some footer content for our web application.

Create a new ui/html/footer.partial.tmpl file and add a named template called footer like so:

$ touch ui/html/footer.partial.tmpl
File: ui/html/footer.partial.tmpl
{{define "footer"}}
<footer>Powered by <a href='https://golang.org/'>Go</a></footer>
{{end}}

Then update the base template so that it invokes the footer using the {{template "footer" .}} action:

File: ui/html/base.layout.html
{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "title" .}} - Snippetbox</title>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        <nav>
            <a href='/'>Home</a>
        </nav>
        <section>
            {{template "body" .}}
        </section>
        <!-- Invoke the footer template -->
        {{template "footer" .}}
    </body>
</html>
{{end}}

Finally, we need update the home handler to include the new ui/html/footer.partial.tmpl file when parsing the template files:

File: cmd/web/handlers.go
package main

...

func home(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)
        return
    }

    // Include the footer partial in the template files.
    files := []string{
        "./ui/html/home.page.tmpl",
        "./ui/html/base.layout.tmpl",
        "./ui/html/footer.partial.tmpl",
    }

    ts, err := template.ParseFiles(files...)
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
        return
    }

    err = ts.Execute(w, nil)
    if err != nil {
        log.Println(err.Error())
        http.Error(w, "Internal Server Error", 500)
    }
}

...

Once you restart the server, your base template should now invoke the footer template and your home page should look like this:

02.08-03.png


Additional Information

The Block Action

In the code above we've used the {{template}} action to invoke one template from another. But Go also provides a {{block}}...{{end}} action which you can use instead. This acts like the {{template}} action, except it allows you to specify some default content if the template being invoked doesn't exist in the current template set.

In the context of a web application, this is useful when you want to provide some default content (such as a sidebar) which individual pages can override on a case-by-case basis if they need to.

Syntactically you use it like this:

{{define "base"}}
    <h1>An example template</h1>
    {{block "sidebar" .}}
        <p>My default sidebar content</p>
    {{end}}
{{end}}

But — if you want — you don't need to include any default content between the {{block}} and {{end}} actions. In that case, the invoked template acts like it's 'optional'. If the template exists in the template set, then it will be rendered. But if it doesn't, then nothing will be displayed.