在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”,因为它是当前登录用户的名称。但我不能直接传递名称,因为有only one可能的“点”参数管道!我能做什么?
答案 0 :(得分:47)
您可以在模板中注册“dict”功能,您可以使用该功能将多个值传递给模板调用。然后,呼叫本身就是这样的:
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
小“dict”帮助程序的代码,包括将其注册为模板函数在这里:
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")
答案 1 :(得分:5)
您可以在模板中定义函数,并将这些函数设置为在数据上定义的闭包,如下所示:
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}}
操场上的 This updated version在当前用户周围输出非常!!
:
*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试图访问同一个模板时它会如何播放......
答案 2 :(得分:2)
基于@ 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")
答案 3 :(得分:1)
最直接的方法(尽管不是最优雅的方法)(特别是对于相对较新的人)是“即时”使用anon结构。早在2012年安德鲁·格朗(Andrew Gerrand)出色的演讲“您可能不了解的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)
请注意,这在技术上不会按原样运行,因为模板需要进行一些较小的清理(即摆脱范围循环最后一行的逗号),但这相当琐碎。将模板的参数包装在匿名结构中可能看起来很繁琐和冗长,但是它的另一个好处是可以明确说明模板执行后将使用的内容。绝对不必为您编写的每个新模板都定义一个命名结构而感到乏味。
答案 4 :(得分:0)
到目前为止我发现的最好(我并不喜欢它)是使用“通用”对容器对参数进行复用和解复用:
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,
}
}
我使用这个技巧来构建我的网站很多,我想知道是否存在一些更惯用的方法来实现同样的目标。
答案 5 :(得分:0)
Ad“......看起来像只调用一个参数的函数。”:
从某种意义上说,每个函数都需要一个参数 - 一个多值的调用记录。使用模板是相同的,“调用”记录可以是原始值,也可以是多值{map,struct,array,slice}。模板可以在任何地方选择“单个”管道参数中使用的{key,field,index}。
在这种情况下,一个就足够了。
答案 6 :(得分:0)
有时,地图是这种情况的快速简便的解决方案,正如其他几个答案所述。由于你经常使用Gophers(因为根据你的其他问题,你关心当前Gopher是否是管理员),我认为它应该有自己的结构:
type Gopher struct {
Name string
IsCurrent bool
IsAdmin bool
}
以下是您的Playground代码的更新:http://play.golang.org/p/NAyZMn9Pep
显然,对于具有额外深度的示例结构进行手工编码会有点繁琐,但由于实际上它们是机器生成的,因此将IsCurrent
和IsAdmin
标记为是直截了当的需要的。
答案 7 :(得分:0)
我接近这个的方法是装饰一般管道:
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
)。
最后,我为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
),但将任意值传递给子模板。