Go模板:是否可以嵌套范围?

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

Go Templates: Are Nested Ranges Possible?

问题

这个问题看起来很简单,但是它让我发疯。

在golang模板中,如何在嵌套范围内引用作用域中更高级的结构元素?

示例:

type Foo struct {
  Id string
  Name string
}

type Bar struct {
  Id string
  Name string
}

var foos []Foo
var bars []Bar

// 用于填充foos和bars的逻辑

在模板中:

{{range .foos}}
  <div>Foo {{.Name}}</div>
  <div>
    {{range ..bars}}
      <div>Bar {{.Name}} <input type="text" name="ids_{{..Id}}_{{.Id}}" /></div>
    {{end}}
  </div>
{{end}}

显然,..bars和..Id是无效的,但是希望我的意图是清楚的。我想遍历Foo和Bar的所有组合,并生成一个由Foo的Id和Bar的Id构建的表单元素的名称。

问题是似乎不可能:

  1. 在foos范围作用域内部访问bars
  2. 在bar的范围作用域内部访问Foo的Id

我有一个临时解决方法,通过在两个结构体中放置大量冗余字段,但是这对我来说看起来非常丑陋,违反了DRY原则,并且总体上感觉非常不对。

在golang模板中是否有实现我想要的功能的方法?

英文:

This one is seemingly simple but it's driving me insane.

How does one go about referencing a struct element higher in the scope within a nested range in golang templates?

Example:

type Foo struct {
  Id string
  Name string
}

type Bar struct {
  Id string
  Name string
}

var foos []Foo
var bars []Bar

// logic to populate both foos and bars

In the template:

{{range .foos}}
  &lt;div&gt;Foo {{.Name}}&lt;/div&gt;
  &lt;div&gt;
    {{range ..bars}}
      &lt;div&gt;Bar {{.Name}} &lt;input type=&quot;text&quot; name=&quot;ids_{{..Id}}_{{.Id}}&quot; /&gt;&lt;/div&gt;
    {{end}}
  &lt;/div&gt;
{{end}}

Obviously ..bars and ..Id don't work, but hopefully my intent is clear. I'd like to iterate through all combinations of Foo and Bar and generate a form element with a name build by both the Foo's Id and the Bar's Id.

The problem is that it seems it is impossible to:

  1. Access bars from inside the scope of the foos range scope
  2. Access Foo's Id from inside the bar's range scope

I have a temporary workaround to this by putting a bunch of redundant fields in both structs, but this seems very ugly to me, violates DRY, and in general feels very wrong.

Is there any way with golang templates to do what I'd like to do?

答案1

得分: 40

是的。我觉得找不到解决方案是因为没有仔细阅读text/template包。如果你正在使用html/template,语法是相同的(并且他们告诉你要阅读text/template ;))。这里是一个完整的工作解决方案,适用于你可能想要做的事情。

Go文件:

package main

import (
    "bytes"
    "io/ioutil"
    "os"
    "strconv"
    "text/template"
)

type Foo struct {
    Id   string
    Name string
}

type Bar struct {
    Id   string
    Name string
}

var foos []Foo
var bars []Bar

func main() {
    foos = make([]Foo, 10)
    bars = make([]Bar, 10)

    for i := 0; i < 10; i++ {
        foos[i] = Foo{strconv.Itoa(i), strconv.Itoa(i)} // just random strings
        bars[i] = Bar{strconv.Itoa(10 * i), strconv.Itoa(10 * i)}
    }

    tmpl, err := ioutil.ReadFile("so.tmpl")
    if err != nil {
        panic(err)
    }

    buffer := bytes.NewBuffer(make([]byte, 0, len(tmpl)))

    output := template.Must(template.New("FUBAR").Parse(string(tmpl)))
    output.Execute(buffer, struct {
        FooSlice []Foo
        BarSlice []Bar
    }{
        FooSlice: foos,
        BarSlice: bars,
    })

    outfile, err := os.Create("output.html")
    if err != nil {
        panic(err)
    }
    defer outfile.Close()
    outfile.Write(buffer.Bytes())
}

注意:你可能可以做一些事情,不将文件加载到中间缓冲区中(使用ParseFiles),我只是复制并粘贴了我为我的一个项目编写的一些代码。

模板文件:

{{ $foos := .FooSlice }}
{{ $bars := .BarSlice }}

{{range $foo := $foos }}
  <div>Foo {{$foo.Name}}</div>
  <div>
    {{range $bar := $bars}}
      <div>Bar {{$bar.Name}} <input type="text" name="ids_{{$foo.Id}}_{{$bar.Id}}" /></div>
    {{end}}
  </div>
{{end}}

这个故事的两个教训是
a)明智地在模板中使用变量,它们是有益的
b)模板中的range也可以设置变量,你不需要仅仅依赖$.

英文:

Yes. I feel as if not finding a solution comes from not reading the text/template package closely enough. If you are using html/template, the syntax is the same (and they tell you to read text/template ;)). Here is a complete working solution for what you might want to do.

Go file:

package main

import (
	&quot;bytes&quot;
	&quot;io/ioutil&quot;
	&quot;os&quot;
	&quot;strconv&quot;
	&quot;text/template&quot;
)

type Foo struct {
	Id   string
	Name string
}

type Bar struct {
	Id   string
	Name string
}

var foos []Foo
var bars []Bar

func main() {
	foos = make([]Foo, 10)
	bars = make([]Bar, 10)

	for i := 0; i &lt; 10; i++ {
		foos[i] = Foo{strconv.Itoa(i), strconv.Itoa(i)} // just random strings
		bars[i] = Bar{strconv.Itoa(10 * i), strconv.Itoa(10 * i)}
	}

	tmpl, err := ioutil.ReadFile(&quot;so.tmpl&quot;)
	if err != nil {
		panic(err)
	}

	buffer := bytes.NewBuffer(make([]byte, 0, len(tmpl)))

	output := template.Must(template.New(&quot;FUBAR&quot;).Parse(string(tmpl)))
	output.Execute(buffer, struct {
		FooSlice []Foo
		BarSlice []Bar
	}{
		FooSlice: foos,
		BarSlice: bars,
	})

	outfile, err := os.Create(&quot;output.html&quot;)
	if err != nil {
		panic(err)
	}
	defer outfile.Close()
	outfile.Write(buffer.Bytes())
}

Note: You can probably do something to not load the file into an intermediate buffer (use ParseFiles), I just copied and pasted some code that I had written for one of my projects.

Template file:

{{ $foos := .FooSlice }}
{{ $bars := .BarSlice }}

{{range $foo := $foos }}
  &lt;div&gt;Foo {{$foo.Name}}&lt;/div&gt;
  &lt;div&gt;
    {{range $bar := $bars}}
      &lt;div&gt;Bar {{$bar.Name}} &lt;input type=&quot;text&quot; name=&quot;ids_{{$foo.Id}}_{{$bar.Id}}&quot; /&gt;&lt;/div&gt;
    {{end}}
  &lt;/div&gt;
{{end}}

The two morals of this story are
a) use variables in templates judiciously, they are beneficial
b) range in templates also can set variables, you do not need to rely solely on $ or .

huangapple
  • 本文由 发表于 2013年7月7日 12:59:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/17509420.html
匿名

发表评论

匿名网友

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

确定