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:
Let’s start by creating a template file at ui/html/pages/home.tmpl
to contain the HTML content for the home page. Like so:
And add the following HTML markup:
Now that we’ve created a template file containing 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 use 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:
There are a couple of important things about this code to point out:
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.If either the
template.ParseFiles()
orts.Execute()
functions return an error, we log the detailed error message and then use thehttp.Error()
function to send a response to the user.http.Error()
is a lightweight helper function which sends a plain text error message and a specific HTTP status code to the user (in our code we send the message"Internal Server Error"
and the status code500
, represented by the constanthttp.StatusInternalServerError
). Effectively, this means that if there is an error, the user will see the messageInternal Server Error
in their browser, but the detailed error message will be recorded in the application log messages.
So, with that said, make sure you’re in the root of your project directory and restart the application:
Then open http://localhost:4000
in your web browser. You should find that the HTML homepage is shaping up nicely.
Template composition
As we add more pages to our 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 prevent duplication and save typing, it’s a good idea to create a base (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.tmpl
file…
And add the following markup (which we want to appear in every page):
Hopefully this feels familiar if you’ve used templating in other languages before. It’s essentially just regular HTML with some extra actions in double curly braces.
We use the {{define "base"}}...{{end}}
action as a wrapper 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 "main" .}}
actions to denote that we want to invoke other named templates (called title
and main
) at a particular location in the HTML.
Now let’s go back to the ui/html/pages/home.tmpl
file and update it to define title
and main
named templates containing the specific content for the home page.
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:
So now, instead of containing HTML directly, our template set contains 3 named templates — base
, title
and main
. We use the ExecuteTemplate()
method to tell Go that we specifically want to respond using the content of the base
template (which in turn invokes our title
and main
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).
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 the primary navigation bar for our web application.
Create a new ui/html/partials/nav.tmpl
file containing a named template called "nav"
, like so:
Then update the base
template so that it invokes the navigation partial using the {{template "nav" .}}
action:
Finally, we need to update the home
handler to include the new ui/html/partials/nav.tmpl
file when parsing the template files:
Once you restart the server, the base
template should now invoke the nav
template and your home page should look like this:
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:
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.