[英]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”,因为它是当前登录用户的名称。 但我不能直接传递名称,因为只有一个可能的“点”参数管道! 我能做些什么?
您可以在模板中注册一个“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")
您可以在模板中定义函数,并将这些函数作为在您的数据上定义的闭包,如下所示:
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)
}
虽然我不确定当几个 goroutine 尝试访问同一个模板时它是如何发挥作用的......
最直接的方法(尽管不是最优雅的)——尤其是对于新手来说——是“即时”使用匿名结构。 早在 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)
请注意,这在技术上不会按原样运行,因为模板需要进行一些小的清理(即去掉范围循环最后一行的逗号),但这相当简单。 将模板的参数包装在匿名结构中可能看起来乏味且冗长,但它具有明确说明模板执行后将使用的内容的额外好处。 绝对比必须为您编写的每个新模板定义一个命名结构更乏味。
我为此问题实现了一个库,它支持类似管道的参数传递和检查。
演示
{{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")
到目前为止,我发现的最好的(我不是很喜欢)是使用“通用”对容器对参数进行复用和解复用:
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,
}
}
我经常使用这个技巧来构建我的网站,我想知道是否存在一些更惯用的方法来实现同样的目标。
广告“......看起来像调用一个只有一个参数的函数。”:
从某种意义上说,每个函数都有一个参数——一个多值调用记录。 使用模板也是一样的,“调用”记录可以是原始值,也可以是多值 {map,struct,array,slice}。 模板可以从任何位置的“单个”管道参数中选择要使用的 {key,field,index}。
IOW,在这种情况下,一个就足够了。
正如其他几个答案中提到的那样,有时地图是解决此类情况的快速简便的解决方案。 由于您经常使用 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,
})
由于HomeData
嵌入在HomeDataContext
中,您的模板将直接访问它(例如,您仍然可以使用.Popular
而不是.HomeData.Popular
)。 此外,您现在可以访问自由格式字段 ( .I
)。
最后,我像这样为HomeDataContext
创建了一个Using
函数。
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
),但将任意值传递给子模板。
根据您的目标, https://github.com/josharian/tstruct (博客文章)可能会有所帮助。 您将定义一个名为UserList
的 Go 结构,使用 tstruct 为其自动生成 FuncMap 助手,然后编写如下内容:
{{ template "userlist" UserList (Users .MostPopular) (Current .CurrentUser) }}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.