I want to run a range within a range using html/template. The sample code (https://go.dev/play/p/fxx61RwIDhd) looks like this:
package main
import (
"html/template"
"os"
)
type Runtest struct {
ID int
SessionType string
}
type Setting struct {
ID int
Type string
}
type templateData struct {
Runtest []*Runtest
Setting []*Setting
}
func main() {
tmpl := template.Must(template.New("nested-range").Parse(`
{{ range .Runtest }}
<h1>Runtest ID: {{ .ID }}, Session Type: {{ .SessionType }}</h1>
<ul>
{{ range .Setting }}
<li>Setting ID: {{ .ID }}, Type: {{ .Type }}</li>
{{ end }}
</ul>
{{ end }}
`))
runtestsList := []*Runtest{
{ID: 1, SessionType: "Type A"},
{ID: 2, SessionType: "Type B"},
}
settingsList := []*Setting{
{ID: 101, Type: "Setting 1"},
{ID: 102, Type: "Setting 2"},
{ID: 201, Type: "Setting X"},
{ID: 202, Type: "Setting Y"},
}
data := templateData{
Runtest: runtestsList,
Setting: settingsList,
}
err := tmpl.Execute(os.Stdout, data)
if err != nil {
panic(err)
}
}
I get the following error when executing the code:
panic: template: nested-range:5:13: executing "nested-range" at <.Setting>: can't evaluate field Setting in type *main.Runtest
It seems like the template engine sees {{ range .Setting }} as part of {{ range .Runtest }}.
If you try it with just one range the code will work: https://go.dev/play/p/CUOxnBfvAo1
Also one range after the other: https://go.dev/play/p/mge2JsQYOYD
Is it possible to run a range within range using data that is not part of the first range data?
It is a scoping problem. range
starts a new scope with the iteration variable being .
. That is, when you have
{{range .Runtest}}
// Here . points to the current element of the Runtest
You can still refer to the variables in the global scope using $
prefix:
{{ range .Runtest }}
<h1>Runtest ID: {{ .ID }}, Session Type: {{ .SessionType }}</h1>
<ul>
{{ range $.Setting }}
Alternatively, you can define loop variables yourself. This is especially useful if you have multiple levels of range
and you need to access variables defined in one of the enclosing ones:
{{ range $key1, $value1 := .someList }}
{{ range $key2, $value2 := $value1.otherList }}
// Here, you can access both $value1 and $value2,
// as well as the global scope $.GlobalScopeVar