Has anyone implemented a solution for managing view bindings while utilizing Go's html/template
? Specifically, I'm hoping to find something that allows me to do things like:
Site.Title
CurrentURL
Render
step, simply provide the http.Handler
-specific variables, to then be combined and provided to the template.An example existing application would be something like this (I use unrolled/render
for layout inheritance, but that is replaceable):
package main
import (
"log"
"net"
"net/http"
"os"
"strings"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/unrolled/render"
)
type HelloBinding struct {
Name string
}
func helloHandler(render *render.Render) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_ = render.HTML(w, http.StatusOK, "hello", &HelloBinding{
Name: "World!",
})
}
}
func main() {
port, ok := os.LookupEnv("PORT")
if !ok {
port = "8080"
}
render := render.New(render.Options{
Directory: "templates",
Layout: "layout",
Extensions: []string{".html"},
IsDevelopment: true,
})
r := chi.NewMux()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Get("/", helloHandler(render))
httpServer := &http.Server{
Addr: net.JoinHostPort("", port),
Handler: r,
}
log.Printf("http server listening at %s
", httpServer.Addr)
if err := httpServer.ListenAndServe(); err != nil {
log.Panic(err)
}
}
<html>
<head>
<title></title>
</head>
<body>
{{ yield }}
</body>
</html>
And a shared view
Hello, {{ .Name }}
In an ideal solution, something like this would be possible:
Warning: Pseudo code
package main
import (
"log"
"net"
"net/http"
"os"
"strings"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/unrolled/render"
)
type GlobalBinding struct {
Title string
}
type RequestBinding struct {
CurrentURL string
}
type HelloBinding struct {
Name string
}
func helloHandler(render *render.Render) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_ = render.HTML(w, http.StatusOK, "hello", &HelloBinding{
Name: "World!",
})
}
}
func main() {
port, ok := os.LookupEnv("PORT")
if !ok {
port = "8080"
}
render := render.New(render.Options{
Directory: "templates",
Layout: "layout",
Extensions: []string{".html"},
IsDevelopment: true,
})
// Binds data to be used
render.Bind(GlobalBindings{
Title: "My Site",
})
r := chi.NewMux()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
// Binds data for the request context only
r.Use(func(next http.Handler) http.Handler {
return func(w http.ResponseWriter, r *http.Request) {
render.BindContext(r.Context, RequestBinding{
CurrentURL: r.URL.String(),
})
next(w, r)
}
})
r.Get("/", helloHandler(render))
httpServer := &http.Server{
Addr: net.JoinHostPort("", port),
Handler: r,
}
log.Printf("http server listening at %s
", httpServer.Addr)
if err := httpServer.ListenAndServe(); err != nil {
log.Panic(err)
}
}
Allowing me to change things like that layout to:
<html>
<head>
<title>{{ .Global.Title }}</title>
</head>
<body>
{{ .CurrentURL }}
{{ yield }}
</body>
</html>
And things are merged and bound without much thought by the individual handler.
Hope you guys have some solutions! I've been struggling at this for a while.
</div>
Not in html/template
but consider using quicktemplates
https://github.com/valyala/quicktemplate
It's designed around regular code so your renderers are just functions that can take arbitrary parameters (and interfaces). You can import packages and call regular functions and access global variables.
It's a lot more comfortable to work with than the builtin templating engine, plus you get static type checking. The only downside is you need to rebuild/restart after editing to reflect changes.