In a Go template, sometimes the way to pass the right data to the right template feels awkward to me. Calling a template with a pipeline parameter looks like calling a function with only one parameter.
Let's say I have a site for Gophers about Gophers. It has a home page main template, and a utility template to print a list of Gophers.
http://play.golang.org/p/Jivy_WPh16
Output :
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> Dewey
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
Now I want to add a bit of context in the subtemplate : format the name "Dewey" differently inside the list because it's the name of the currently logged user. But I can't pass the name directly because there is only one possible "dot" argument pipeline! What can I do?
You could register a "dict" function in your templates that you can use to pass multiple values to a template call. The call itself would then look like that:
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
The code for the little "dict" helper, including registering it as a template func is here:
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i+=2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
}).ParseGlob("templates/*.html")
You can define functions in your template, and have these functions being closures defined on your data like this:
template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User);},}
Then, you can simply call this function in your template:
{{define "sub"}}
{{range $y := .}}>> {{if isUser $y}}!!{{$y}}!!{{else}}{{$y}}{{end}}
{{end}}
{{end}}
This updated version on the playground outputs pretty !!
around the current user:
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> !!Dewey!!
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
EDIT
Since you can override functions when calling Funcs
, you can actually pre-populate the template functions when compiling your template, and update them with your actual closure like this:
var defaultfuncs = map[string]interface{} {
"isUser": func(g Gopher) bool { return false;},
}
func init() {
// Default value returns `false` (only need the correct type)
t = template.New("home").Funcs(defaultfuncs)
t, _ = t.Parse(subtmpl)
t, _ = t.Parse(hometmpl)
}
func main() {
// When actually serving, we update the closure:
data := &HomeData{
User: "Dewey",
Popular: []Gopher{"Huey", "Dewey", "Louie"},
Active: []Gopher{"Huey", "Louie"},
Recent: []Gopher{"Louie"},
}
t.Funcs(template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User); },})
t.ExecuteTemplate(os.Stdout, "home", data)
}
Although I am not sure how that plays when several goroutines try to access the same template...
The working example
The most straightforward method (albeit not the most elegant) - especially for someone relatively new to go - is to use anon structs "on the fly". This was documented/suggested as far back as Andrew Gerrand's excellent 2012 presentation "10 things you probably don't know about go"
https://talks.golang.org/2012/10things.slide#1
Trivial example below :
// define the template
const someTemplate = `insert into {{.Schema}}.{{.Table}} (field1, field2)
values
{{ range .Rows }}
({{.Field1}}, {{.Field2}}),
{{end}};`
// wrap your values and execute the template
data := struct {
Schema string
Table string
Rows []MyCustomType
}{
schema,
table,
someListOfMyCustomType,
}
t, err := template.New("new_tmpl").Parse(someTemplate)
if err != nil {
panic(err)
}
// working buffer
buf := &bytes.Buffer{}
err = t.Execute(buf, data)
Note that this won't technically run as-is, since the template needs some minor cleaning-up (namely getting rid of the comma on the last line of the range loop), but that's fairly trivial. Wrapping the params for your template in an anonymous struct may seem tedious and verbose, but it has the added benefit of explicitly stating exactly what will be used once the template executes. Definitely less tedious than having to define a named struct for every new template you write.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With