When I started learning about Golang
templates much of what I read
was around their syntax and feature set. It didn’t take long, but I
found myself struggling to do things that struck me as being pretty
basic. So I figured I’d write down a few things I’ve learned.
By no means is this intended to be a proper (or even correct)
howto on Golang templates, rather it’s just what I’ve learned so far.
Here’s what I was trying to accomplish:
Have a directory of templates (header.html, foobar.html, etc).
Have a directory of static files (css, images, etc).
Use some templates as full pages (about.html, hello.html).
Use some templates as partials (header.html, footer.html).
Serve static content in a manner similar to
http.FileServer.
Exclude templates from the static files being served.
Support custom template functions.
Compile everything into a single static binary (including templates
and static files).
What I used
Here’s a quick punch list of the things I used and what they are for.
Inside the static folder I’d put static assets, things like pictures,
css, javascript. Inside templates I’d put the full and partial
templates, and template functions would go inside functions.go. The
application itself would then be main.go.
packagemain//go:generate go-bindata-assetfs static/... templates/...
import (
"html/template""log""net/http""github.com/julienschmidt/httprouter")
// Model of stuff to render a page
typeModelstruct {
TitlestringNamestring}
// Templates with functions available to them
vartemplates = template.New("").Funcs(templateMap)
// Parse all of the bindata templates
funcinit() {
for_, path:=rangeAssetNames() {
bytes, err:=Asset(path)
iferr!=nil {
log.Panicf("Unable to parse: path=%s, err=%s", path, err)
}
templates.New(path).Parse(string(bytes))
}
}
// Render a template given a model
funcrenderTemplate(whttp.ResponseWriter, tmplstring, pinterface{}) {
err:=templates.ExecuteTemplate(w, tmpl, p)
iferr!=nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
// Well hello there
funchello(whttp.ResponseWriter, r*http.Request, pshttprouter.Params) {
model:=Model{Name: ps.ByName("name")}
renderTemplate(w, "templates/hello.html", &model)
}
// The server itself
funcmain() {
// mux handler
router:=httprouter.New()
// Example route that takes one rest style option
router.GET("/hello/:name", hello)
// Serve static assets via the "static" directory
router.ServeFiles("/static/*filepath", assetFS())
// Serve this program forever
log.Fatal(http.ListenAndServe(":8080", router))
}
go-bindata does a great job
of compiling static assets into a single binary, and
go-bindata-assetfs
makes it easy to serve those assets over http. I wasn’t able to find
an easy way to expose ExecuteTemplate in a manner that made it
possible to add a function map, but a quick for loop in init() seems
to do the trick. Partials are managed here purely using upstream
documentation. I have not yet sorted out how to exclude the templates
from being served over http (hence the strike through). A working
example if this sample project is on Github: