Hey Gophers! In the last couple of days I've been struggling how to pass data into an HTML template effectively. Let me explain my thoughts:
Say you have a simple search functionality on your website. There is a handler responsible for displaying search results for a given query. Surely there is a lot more to display than just the search results. In the header you can see some information of the currently logged in user account. In the footer you can see the last five testimonials from people on twitter. The header and footer are present on all pages of the website. You would have to query and pass the data for the user and testimonials into every handler which executes a template.
func search(w http.ResponseWriter, r *http.Request) {
results := querySearch()
searchTemplate.Execute(w, map[string]interface{}{
"Results": results,
"User": currentUser(),
"Testimonials": currentTestimonials(),
})
}
In my opinion the handler displaying the search results is not responsible for anything other than the results. The data for the header and footer should come from some abstraction level above. As an example, we do similar abstractions with middleware/adapters. You have one middleware for checking if a user is logged in or not and wrap all handlers which depend on a logged in user state. You don't check for authentication in all handlers manually.
I've come up with an idea which may be a (good or not so good) solution to this problem: https://play.golang.org/p/ys64OyXAjg This idea is inspired by the http.RoundTripper inferface. (Thanks /u/campoy for your talk about interfaces)
There is a Renderer interface (apologize the bad naming) which can writes data
to an io.Writer
, it's unspecified how.
type Renderer interface {
Render(w io.Writer, data map[string]interface{})
}
If you go and extend an html/template.Template to satisfy this interface, you can apply some sort of middleware to it.
Create your own type which embeds a Renderer and implements the Renderer interface:
type titleRenderer struct {
title string
renderer Renderer
}
func (r *titleRenderer) Render(w io.Writer, data map[string]interface{}) {
data["Title"] = r.title
r.renderer.Render(w, data)
}
Then you can wrap an original renderer into a titleRenderer, which adds Title
to the data map whenever the original Render function is called.
With that, the additional data like the current logged in user or the testimonials can be passed in, in an abstraction level above.
Thoughts and alternative ideas how to solve this issue are very welcome. Thanks!
评论:
shovelpost:
egonelbre:There is a Renderer interface (apologize the bad naming)
Renderer is the idiomatic name in this case.
In my opinion the handler displaying the search results is not responsible for anything other than the results.
I agree and that's why context exists. The current user should be passed down in the handler request context from the middlewhere that is responsible for that.
Also If you want to inject dependencies to your handlers it is a good pattern to do something like this:
type MyApp struct { // dependencies go here } func (app *MyApp) search(w http.ResponseWriter, r *http.Request) { // Grab user from context // app.SearchService.Search(query) // Render search results and other stuff based on the current user context. }
Other than that, the Renderer interface seems like a good idea to me which can complement the above techniques.
You can also use https://golang.org/pkg/html/template/#Template.Funcs to return common types.
