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}} <div>Foo {{.Name}}</div> <div> {{range ..bars}} <div>Bar {{.Name}} <input type="text" name="ids_{{..Id}}_{{.Id}}" /></div> {{end}} </div> {{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:
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?
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 ( "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()) }
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 }} <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}}
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 .
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