通过堆栈管理和提供html / template数据绑定

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:

  • Set global during setup Site.Title
  • Provide request-scoped variables, like CurrentURL
  • Then, at the 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.