This was what we achieved in part 2 of the tutorial.
Let's separate our index.html file into smaller digestible chunks by splitting the header, nav, body and footer into their own template file.
{{ define "header" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Go Bootstrap Example | {{.Title}}</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
{{ end }}
{{ define "nav"}}
<nav class='nav nav-pills justify-content-center p-3'>
<a class='nav-link active' href='/'>Home</a>
<a class='nav-link' href='/about/'>About</a>
</nav>
{{ end }}
{{ define "footer" }}
<footer class="p-5 text-center">
This is the {{.Title}} footer.
</footer>
</html>
{{ end }}
{{define "index"}}
{{template "header"}}
{{template "nav"}}
<main class="jumbotron">
<div class="text-center p-5">
<h1>{{.Title}}</h1>
<p class="lead">{{.Body}}</p>
</div>
</main>
{{template "footer"}}
{{end}}
In Go, we surround template declarations with a "define" and "end" action.
The different templates are given a name by providing a string constant to the "define" action.
We then nest the template definitions in index.html using the template action and provide the associated name (header, footer, nav) with it.
There's one small problem. Our code does not recognise the other HTML partials.
If you run the program now, you will see a blank page.
Let's fix that.
1. Declare a variable in main.go to store all the HTML partials.
...
var templates = template.Must(template.ParseFiles("templates/header.html", "templates/nav.html", "templates/index.html", "templates/footer.html"))
...
2. Create a function to render the templates.
func renderTemplate(w http.ResponseWriter, tmpl string, page *Data) {
err := templates.ExecuteTemplate(w, tmpl, page)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
The renderTemplate function accepts three arguments.
3. Refactor the handlers to use the renderTemplate function.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
page := &Data{Title:"Home page", Body:"Welcome to our brand new home page."}
renderTemplate(w, "index", page)
}
func AboutHandler(w http.ResponseWriter, r *http.Request) {
page := &Data{Title:"About page", Body:"This is our brand new about page."}
renderTemplate(w, "index", page)
}
So far so good, however, the templates are not caching efficiently.
We have to add the new template's location to our templates declaration whenever we add a new HTML partial.
Such a pain.
Thankfully, Go has a ParseGlob function that can parse multiple files in one go. (Pun intended)
Replace the templates declaration with the following code.
ar templates = template.Must(template.ParseGlob("templates/*"))
Now, whenever we add a new HTML partial to the templates folder, Go will automatically compile and cache it.
You may have noticed that the page titles are not showing in header.html and footer.html.
This can be fixed with a magical dot syntax in the template declarations.
{{define "index"}}
{{template "header" .}}
{{template "nav"}}
<main class="jumbotron">
...
</main>
{{template "footer" .}}
{{end}}
The dot is simply telling Go to pass in the current value of .Title to the header and footer templates.
Let's also add the active class in our nav.html to only the selected page.
{{define "index"}}
...
{{template "nav" .}}
...
{{end}}
{{ define "nav"}}
<nav class='nav nav-pills justify-content-center p-3'>
<a class='nav-link {{if (eq .Title "Home page")}}active{{end}}' href='/'>Home</a>
<a class='nav-link {{if (eq .Title "About page")}}active{{end}}' href='/about/'>About</a>
</nav>
{{ end }}
Run the program to see the active class in action.
Click here to view the code
Having to run the program after every change is not very efficient at all.
We will make use of two libraries and one chrome extension to automate the build and reload process.
Add both libraries to the application.
$ go get github.com/omeid/go-livereload/cmd/livereload github.com/codegangsta/gin
Start livereload in the working folder.
$ livereload ./
Run the application using gin. (We are setting gin to listen on port 8080)
$ gin --appPort 8080 run main.go
Activate the Livereload extension in Chrome.
Visit http://localhost:3000/. We are using port 3000 as gin uses that to listen for changes.
Go ahead and make some changes to main.go. I'll wait.
My journey with Go started three months ago.
However, in those three months, I find Go to be a practical and beginner friendly language to dive into.
I was initially inspired by this blog post that a friend had sent to me.
Gojek mentioning (urban legend or not) that their CTO re-wrote the entire codebase in Go within three nights piqued my curiosity further.
Would I have chance to use Go in my day to day work? Maybe not.
Would I develop applications using Go in the future? Most definitely.
Take a tour of Go. You might like it.