Comparing two variables inside a Go template

In the data I am passing to my template, I have two variables Type

and Res.Type

. I want to compare to preselecting an option for my select box.

To illustrate my problem, I 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 an option "FindAllString"

but only generates an 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 it. I saw that you can add a function to the template, but I believe there should be an easier way for that.

+3


source to share


1 answer


The problem is that the action {{range}}

changes (sets) point ( .

) even if you are using loop variables ( $key

and $value

) in your case. Internally, the {{range}}

point is set to the current element.

And inside {{range}}

you write:

{{ if eq $key .Res.Type }}

      

Since the values ​​in your loop are values string

, it .Res.Type

is an error because there is no field Res

or value method string

(the current element denoted by a dot .

).

Use a sign $

to not refer to the loop value, but to the parameter passed when the template was executed:

{{ if eq $key $.Res.Type }}

      

This will work, but will not give the desired result, since you have a typo:

res := &Result{Type: "findAllString"}

      

Use a capital letter in Result

as your map types

also contains capitalized values:



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 can 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 can simply pass os.Stdout

as the author to execute the template and you will see the result in 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 information: pipelines with golang template templates

+2


source







All Articles