Compare two variables inside Go template

huangapple go评论95阅读模式
英文:

Compare two variables inside Go template

问题

在我传递给模板的数据中,我有两个变量TypeRes.Type,我想将它们进行比较,以预先选择选择字段的选项。

为了说明我的问题,我创建了这个简化版本:

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())
}

它应该选择"FindAllString"选项,但实际上只生成了错误:

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

当我只比较两个普通字符串时,它可以工作,但我想知道是否有一种惯用的方法来做到这一点。我看到你可以在模板中添加一个函数,但我觉得一定有一种更简单的方法。

英文:

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 (
	&quot;bufio&quot;
	&quot;bytes&quot;
	&quot;html/template&quot;
	&quot;log&quot;
)

type Result struct{ Type string }

func main() {
	types := map[string]string{
		&quot;FindAllString&quot;:      &quot;FindAllString&quot;,
		&quot;FindString&quot;:         &quot;FindString&quot;,
		&quot;FindStringSubmatch&quot;: &quot;FindStringSubmatch&quot;,
	}
	res := &amp;Result{Type: &quot;findAllString&quot;}

	templateString := `
	&lt;select name=&quot;type&quot;&gt;
		{{ range $key,$value := .Types }}
			{{ if eq $key .Res.Type }}
				&lt;option value=&quot;{{$key}}&quot; selected&gt;{{$value}}&lt;/option&gt;
			{{ else }}
				&lt;option value=&quot;{{$key}}&quot;&gt;{{$value}}&lt;/option&gt;
			{{ end }}
		{{ end }}
	&lt;/select&gt;`
	t, err := template.New(&quot;index&quot;).Parse(templateString)
	if err != nil {
		panic(err)
	}
	var b bytes.Buffer
	writer := bufio.NewWriter(&amp;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 &quot;FindAllString&quot; option but it only generates the error

panic: template: index:4:21: executing &quot;index&quot; at &lt;.Res.Type&gt;: can&#39;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.

答案1

得分: 14

问题在于{{range}}操作会改变(设置)点号(.),即使在你的情况下使用了循环变量($key$value)。在{{range}}内部,点号被设置为当前元素。

{{range}}内部,你写的是:

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

由于循环中的值是字符串值,.Res.Type是一个错误,因为字符串值(由点号.表示的当前元素)没有Res字段或方法。

使用$符号来引用模板执行时传递的参数,而不是引用循环值:

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

这样就可以工作了,但是不会给你想要的输出,因为你有一个拼写错误:

res := &amp;Result{Type: &quot;findAllString&quot;}

Result中使用大写字母,因为你的types映射也包含大写字母的值:

res := &amp;Result{Type: &quot;FindAllString&quot;}

使用这个代码就可以得到你想要的输出(在Go Playground上试一试):

2009/11/10 23:00:00 
    &lt;select name=&quot;type&quot;&gt;
                &lt;option value=&quot;FindAllString&quot; selected&gt;FindAllString&lt;/option&gt;
                &lt;option value=&quot;FindString&quot;&gt;FindString&lt;/option&gt;
                &lt;option value=&quot;FindStringSubmatch&quot;&gt;FindStringSubmatch&lt;/option&gt;
    &lt;/select&gt;

还要注意,你可以简单地这样写循环:

{{range $key, $value := .Types}}
    &lt;option value=&quot;{{$key}}&quot;{{if eq $key $.Res.Type}} selected{{end}}&gt;{{.}}&lt;/option&gt;
{{end}}

还要注意,为了测试目的,你可以将os.Stdout作为模板执行的写入器,这样你就可以在控制台上看到结果,而不必创建和使用缓冲区,例如:

err = t.Execute(os.Stdout, struct {
	Types map[string]string
	Res   *Result
}{types, res})

Go Playground上尝试简化版本。

阅读这个答案以获取更多见解:https://stackoverflow.com/questions/42507958/golang-template-engine-pipelines/42508255#42508255

英文:

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 := &amp;Result{Type: &quot;findAllString&quot;}

Use capital letter in Result as your types map also contains values with capital letter:

res := &amp;Result{Type: &quot;FindAllString&quot;}

With this you get the desired output (try it on the Go Playground):

2009/11/10 23:00:00 
    &lt;select name=&quot;type&quot;&gt;
                &lt;option value=&quot;FindAllString&quot; selected&gt;FindAllString&lt;/option&gt;
                &lt;option value=&quot;FindString&quot;&gt;FindString&lt;/option&gt;
                &lt;option value=&quot;FindStringSubmatch&quot;&gt;FindStringSubmatch&lt;/option&gt;
    &lt;/select&gt;

Also note that you could simply write the loop like this:

{{range $key, $value := .Types}}
    &lt;option value=&quot;{{$key}}&quot;{{if eq $key $.Res.Type}} selected{{end}}&gt;{{.}}&lt;/option&gt;
{{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: https://stackoverflow.com/questions/42507958/golang-template-engine-pipelines/42508255#42508255

huangapple
  • 本文由 发表于 2017年6月24日 16:34:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/44734529.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定