In the data I pass to my template I have the two variables Type
and Res.Type
I want to compare to preselect an option for my select field.
To illustrate my problem I have created this simplified version:
package main
import (
"bufio"
"bytes"
"html/template"
"log"
)
type Result struct{ Type string }
func main() {
types := map[string]string{
"FindAllString": "FindAllString",
"FindString": "FindString",
"FindStringSubmatch": "FindStringSubmatch",
}
res := &Result{Type: "findAllString"}
templateString := `
<select name="type">
{{ range $key,$value := .Types }}
{{ if eq $key .Res.Type }}
<option value="{{$key}}" selected>{{$value}}</option>
{{ else }}
<option value="{{$key}}">{{$value}}</option>
{{ end }}
{{ end }}
</select>`
t, err := template.New("index").Parse(templateString)
if err != nil {
panic(err)
}
var b bytes.Buffer
writer := bufio.NewWriter(&b)
err = t.Execute(writer, struct {
Types map[string]string
Res *Result
}{types, res})
if err != nil {
panic(err)
}
writer.Flush()
log.Println(b.String())
}
It should select the "FindAllString"
option but it only generates the error
panic: template: index:4:21: executing "index" at <.Res.Type>: can't evaluate field Res in type string
goroutine 1 [running]:
panic(0x53f6e0, 0xc4200144c0)
/usr/local/go/src/runtime/panic.go:500 +0x1a1
main.main()
/home/tobias/ngo/src/github.com/gamingcoder/tmp/main.go:41 +0x523
exit status 2
When I just compare two normal strings it works but I want to know if there is an idomatic way to do this. I have seen that you could add a function to the template but I feel that there must be a simpler way for this.
The problem is that the {{range}}
action changes (sets) the dot (.
) even if you use loop variables ($key
and $value
) in your case. Inside a {{range}}
the dot is set to the current element.
And inside {{range}}
you write:
{{ if eq $key .Res.Type }}
Since values in your loop are string
values, .Res.Type
is an error, because there is no Res
field or method of a string
value (the current element denoted by the dot .
).
Use the $
sign to not refer to the loop value, but to the param passed to the template execution:
{{ if eq $key $.Res.Type }}
This will work, but won't give you the desired output, as you have a typo:
res := &Result{Type: "findAllString"}
Use capital letter in Result
as your types
map also contains values with capital letter:
res := &Result{Type: "FindAllString"}
With this you get the desired output (try it on the Go Playground):
2009/11/10 23:00:00
<select name="type">
<option value="FindAllString" selected>FindAllString</option>
<option value="FindString">FindString</option>
<option value="FindStringSubmatch">FindStringSubmatch</option>
</select>
Also note that you could simply write the loop like this:
{{range $key, $value := .Types}}
<option value="{{$key}}"{{if eq $key $.Res.Type}} selected{{end}}>{{.}}</option>
{{end}}
Also note that for testing purposes you may simply pass os.Stdout
as the writer for template execution, and you'll see the result on your console without having to create and use a buffer, e.g.:
err = t.Execute(os.Stdout, struct {
Types map[string]string
Res *Result
}{types, res})
Try the simplified version on the Go Playground.
Read this answer for more insights: golang template engine pipelines