Шаблоны Go: Возможны ли вложенные диапазоны?

Этот, казалось бы, простой, но это сводит меня с ума.

Как сделать ссылку на элемент структуры выше в области внутри вложенного диапазона в шаблонах golang?

Пример:

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

В шаблоне:

{{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, так и с идентификатором строки.

Проблема в том, что кажется невозможным:

  • Доступные полосы внутри области действия диапазона foos
  • Доступ к Foo-идентификатору из диапазона диапазона штриховок

У меня есть временное обходное решение для этого, добавив кучу лишних полей в обеих структурах, но это кажется мне очень уродливым, нарушает СУХОЙ, и в целом кажется очень неправильным.

Есть ли способ с шаблонами golang делать то, что я хотел бы сделать?

Ответ 1

Да. Я чувствую, что не нахожу решение, потому что не читаю пакет text/template достаточно близко. Если вы используете html/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) диапазон в шаблонах также может устанавливать переменные, вам не нужно полагаться исключительно на $ или .