使用Go模板/HTML迭代生成表格从结构体中

11

给定一个结构体集合,如何使用“range”模板迭代器打印出一个表格,为每个结构体分配一行,为每个字段值分配一列,而不需要显式命名字段?

container := []Node

type Node struct {
    Contact_id        int
    Employer_id       int
    First_name        string
    Middle_name       string
    Last_name         string
}

模板代码:

{{range .container}}

<tr>
<td>{{.Prefix}}</td>
<td>{{.First_name}}</td>
<td>{{.Middle_name}}</td>
<td>{{.Last_name}}</td>

<td>{{.Contact_id}}</td>
<td>{{.Employer_id}}</td>

</tr>
{{end}}

当我尝试使用迭代器遍历这些值时


{{range .container}}
 {{range .}}
 <td>{{.}}</td> 
{{end}}
{{end}}

有人告诉我无法迭代值。 有没有简便的方法可以做到这一点?


2
你的代码中,.container.Nodes 是什么?如果你想要遍历 container,只需要使用 .container 即可。 - nemo
我的错误,我已经更正了上面的示例。 - Derek
1个回答

16
使用html/template,您无法迭代结构体中的字段。在文档中,您可以阅读到:

{{range pipeline}} T1 {{end}}
管道的值必须是数组、切片、映射或通道。

也就是说,管道不能是结构体。要么您需要:
  • 使用一个中间类型,例如[][]interface{},作为容器变量传递给模板
  • 像您展示的那样单独输入每个单元格
  • 创建一个模板函数,将结构体值转换为您可以迭代的某种类型
由于结构体在编译时定义并且不会在运行时改变其结构,因此迭代是不必要的,而且在模板中不会使事情更清晰。我建议避免这样做。 编辑 但有时反射是一件好事。Brenden还指出,您实际上可以让range迭代从函数返回的值。如果使用反射,这将是最简单的方法。
使用模板函数的完整工作示例:
package main

import (
    "html/template"
    "os"
    "reflect"
)

type Node struct {
    Contact_id  int
    Employer_id int
    First_name  string
    Middle_name string
    Last_name   string
}

var templateFuncs = template.FuncMap{"rangeStruct": RangeStructer}

// In the template, we use rangeStruct to turn our struct values
// into a slice we can iterate over
var htmlTemplate = `{{range .}}<tr>
{{range rangeStruct .}} <td>{{.}}</td>
{{end}}</tr>
{{end}}`

func main() {
    container := []Node{
        {1, 12, "Accipiter", "ANisus", "Nisus"},
        {2, 42, "Hello", "my", "World"},
    }

    // We create the template and register out template function
    t := template.New("t").Funcs(templateFuncs)
    t, err := t.Parse(htmlTemplate)
    if err != nil {
        panic(err)
    }

    err = t.Execute(os.Stdout, container)
    if err != nil {
        panic(err)
    }

}

// RangeStructer takes the first argument, which must be a struct, and
// returns the value of each field in a slice. It will return nil
// if there are no arguments or first argument is not a struct
func RangeStructer(args ...interface{}) []interface{} {
    if len(args) == 0 {
        return nil
    }

    v := reflect.ValueOf(args[0])
    if v.Kind() != reflect.Struct {
        return nil
    }

    out := make([]interface{}, v.NumField())
    for i := 0; i < v.NumField(); i++ {
        out[i] = v.Field(i).Interface()
    }

    return out
}

输出:

<tr>
    <td>1</td>
    <td>12</td>
    <td>Accipiter</td>
    <td>ANisus</td>
    <td>Nisus</td>
</tr>
<tr>
    <td>2</td>
    <td>42</td>
    <td>Hello</td>
    <td>my</td>
    <td>World</td>
</tr>

Playground


基本上的想法是让应用程序在最小干预下从查询响应转换为数据表。我直接从 SQL 查询中扫描数据到一个结构体中,然后反射出一个标题列表,在模板中迭代这些标题以创建 thead。 感谢您的回复,现在我可以停止对此进行头痛了。我将把结构体移植到值数组中,并对其进行迭代。 - Derek
然后,您可以使用reflect包来填充一个中间的[][]interface{}变量,将其传递给模板,就像您为标题使用反射一样。如果有时间,我会添加一个示例。 - ANisus
1
编写自己的模板方法rangeStruct,使用http://golang.org/pkg/html/template/#Template.Funcs,并像上面提到的那样使用`reflect`。例如:https://github.com/brendensoares/revel/blob/master/template.go - Brenden
@Brenden 我之前没有考虑到这个可能性,所以我对此持否定态度。但是现在我尝试了一下,发现确实可以让模板函数返回一个结构体来进行迭代!这是一个更好的解决方案。我会加上它。 - ANisus
@Derek 我按照Brenden的建议添加了一个使用自定义模板函数的工作示例。我忽略了可以对函数返回值进行范围操作的可能性。 - ANisus
3
太棒了!我相信很多人会觉得这很有用。 - twotwotwo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接