提问人:Deleplace 提问时间:8/16/2013 最后编辑:ROMANIA_engineerDeleplace 更新时间:8/15/2023 访问量:43795
调用具有多个管道参数的模板
Calling a template with several pipeline parameters
问:
在 Go 模板中,有时将正确的数据传递到正确的模板的方式对我来说很尴尬。使用管道参数调用模板就像调用只有一个参数的函数。
假设我有一个关于 Gophers 的 Gophers 网站。它有一个主页主模板,以及一个用于打印 Gophers 列表的实用程序模板。
http://play.golang.org/p/Jivy_WPh16
输出:
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> Dewey
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
现在我想在子模板中添加一些上下文:在列表中以不同的方式格式化名称“Dewey”,因为它是当前登录用户的名称。但是我不能直接传递名称,因为只有一个可能的“点”参数管道!我能做些什么?
- 显然,我可以将子模板代码复制粘贴到主模板中(我不想这样做,因为它放弃了拥有子模板的所有兴趣)。
- 或者我可以用访问器处理某种全局变量(我也不想)。
- 或者我可以为每个模板参数列表创建一个新的特定结构类型(不是很好)。
答:
到目前为止,我发现的最好的(我不太喜欢它)是带有“通用”对容器的多路复用和解复用参数:
http://play.golang.org/p/ri3wMAubPX
type PipelineDecorator struct {
// The actual pipeline
Data interface{}
// Some helper data passed as "second pipeline"
Deco interface{}
}
func decorate(data interface{}, deco interface{}) *PipelineDecorator {
return &PipelineDecorator{
Data: data,
Deco: deco,
}
}
我经常使用这个技巧来构建我的网站,我想知道是否存在一些更惯用的方法来实现相同的目的。
您可以在模板中注册一个“dict”函数,该函数可用于将多个值传递给模板调用。然后,调用本身将如下所示:
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
小“dict”助手的代码,包括将其注册为模板 func 在这里:
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i+=2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
}).ParseGlob("templates/*.html")
评论
dict()
dict
interface{}
您可以在模板中定义函数,并将这些函数作为对数据定义的闭包,如下所示:
template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User);},}
然后,您可以简单地在模板中调用此函数:
{{define "sub"}}
{{range $y := .}}>> {{if isUser $y}}!!{{$y}}!!{{else}}{{$y}}{{end}}
{{end}}
{{end}}
操场上的这个更新版本输出非常接近当前用户:!!
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> !!Dewey!!
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
编辑
由于您可以在调用时覆盖函数,因此您实际上可以在编译模板时预填充模板函数,并使用实际闭包更新它们,如下所示:Funcs
var defaultfuncs = map[string]interface{} {
"isUser": func(g Gopher) bool { return false;},
}
func init() {
// Default value returns `false` (only need the correct type)
t = template.New("home").Funcs(defaultfuncs)
t, _ = t.Parse(subtmpl)
t, _ = t.Parse(hometmpl)
}
func main() {
// When actually serving, we update the closure:
data := &HomeData{
User: "Dewey",
Popular: []Gopher{"Huey", "Dewey", "Louie"},
Active: []Gopher{"Huey", "Louie"},
Recent: []Gopher{"Louie"},
}
t.Funcs(template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User); },})
t.ExecuteTemplate(os.Stdout, "home", data)
}
虽然我不确定当几个 goroutines 尝试访问同一个模板时它是如何播放的......
评论
runtime.GoSched()
panic()
广告“......看起来像调用一个只有一个参数的函数。
从某种意义上说,每个函数都采用一个参数 - 多值调用记录。对于模板也是如此,“调用”记录可以是原始值,也可以是多值 {map,struct,array,slice}。模板可以从任何位置的“单个”管道参数中选择要使用的 {key,field,index}。
哎呀,在这种情况下一个就足够了。
评论
有时,地图是解决此类情况的快速简便的解决方案,如其他几个答案中所述。由于您经常使用 Gophers(并且,根据您的另一个问题,您关心当前的 Gopher 是否是管理员),我认为它应该有自己的结构:
type Gopher struct {
Name string
IsCurrent bool
IsAdmin bool
}
以下是 Playground 代码的更新:http://play.golang.org/p/NAyZMn9Pep
显然,手动编码具有额外深度的示例结构有点麻烦,但由于在实践中它们将是机器生成的,因此可以根据需要进行标记。IsCurrent
IsAdmin
我处理这个问题的方法是装饰一般管道:
type HomeData struct {
User Gopher
Popular []Gopher
Active []Gopher
Recent []Gopher
}
通过创建特定于上下文的管道:
type HomeDataContext struct {
*HomeData
I interface{}
}
分配特定于上下文的管道非常便宜。您可以通过复制指向它的指针来访问可能较大的内容:HomeData
t.ExecuteTemplate(os.Stdout, "home", &HomeDataContext{
HomeData: data,
})
由于嵌入在 中,您的模板将直接访问它(例如,您仍然可以 do 和 not )。此外,您现在可以访问自由格式字段 ()。HomeData
HomeDataContext
.Popular
.HomeData.Popular
.I
最后,我做了一个函数。Using
HomeDataContext
func (ctx *HomeDataContext) Using(data interface{}) *HomeDataContext {
c := *ctx // make a copy, so we don't actually alter the original
c.I = data
return &c
}
这允许我保留一个状态(),但将任意值传递给子模板。HomeData
请参见 http://play.golang.org/p/8tJz2qYHbZ。
评论
Using
Data
PipelineDecorator
我为这个问题实现了一个库,它支持类似管道的参数 passing&check。
演示
{{define "foo"}}
{{if $args := . | require "arg1" | require "arg2" "int" | args }}
{{with .Origin }} // Original dot
{{.Bar}}
{{$args.arg1}}
{{ end }}
{{ end }}
{{ end }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" 42 }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" "42" }} // will raise an error
基于@tux21b
我改进了该函数,因此即使不指定索引也可以使用它(只是为了保持将变量附加到模板的方式)
所以现在你可以这样做:
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
或
{{template "userlist" dict .MostPopular .CurrentUser}}
或
{{template "userlist" dict .MostPopular "Current" .CurrentUser}}
但是,如果参数 (.CurrentUser.name) 不是数组,则肯定需要放置索引才能将此值传递给模板
{{template "userlist" dict .MostPopular "Name" .CurrentUser.name}}
查看我的代码:
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values) == 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{})
for i := 0; i < len(values); i ++ {
key, isset := values[i].(string)
if !isset {
if reflect.TypeOf(values[i]).Kind() == reflect.Map {
m := values[i].(map[string]interface{})
for i, v := range m {
dict[i] = v
}
}else{
return nil, errors.New("dict values must be maps")
}
}else{
i++
if i == len(values) {
return nil, errors.New("specify the key for non array values")
}
dict[key] = values[i]
}
}
return dict, nil
},
}).ParseGlob("templates/*.html")
最直接的方法(尽管不是最优雅的)——尤其是对于一个相对较新的人来说——是“即时”使用匿名结构。早在 Andrew Gerrand 2012 年的出色演讲“你可能不知道的 10 件事”中,就已经记录/建议了这一点
https://talks.golang.org/2012/10things.slide#1
下面的简单例子:
// define the template
const someTemplate = `insert into {{.Schema}}.{{.Table}} (field1, field2)
values
{{ range .Rows }}
({{.Field1}}, {{.Field2}}),
{{end}};`
// wrap your values and execute the template
data := struct {
Schema string
Table string
Rows []MyCustomType
}{
schema,
table,
someListOfMyCustomType,
}
t, err := template.New("new_tmpl").Parse(someTemplate)
if err != nil {
panic(err)
}
// working buffer
buf := &bytes.Buffer{}
err = t.Execute(buf, data)
请注意,从技术上讲,这不会按原样运行,因为模板需要一些小的清理(即删除范围循环最后一行的逗号),但这相当微不足道。将模板的参数包装在匿名结构中可能看起来很乏味和冗长,但它还有一个额外的好处,即明确说明模板执行后将使用的内容。绝对没有为你编写的每个新模板定义一个命名结构那么乏味。
根据您的目标,https://github.com/josharian/tstruct(博客文章)可能会有所帮助。您将定义一个名为 的 Go 结构体,使用 tstruct 为其自动生成 FuncMap 助手,然后编写如下内容:UserList
{{ template "userlist" UserList (Users .MostPopular) (Current .CurrentUser) }}
您可以仅通过点将所有数据传递给它。.
{{ template "some" . }}
评论