我看不到使用动态名称调用模板(文本或 html)的方法。例子:
这有效:
{{template "Blah" .}}
此错误与“模板调用中的意外“$BlahVar””:
{{$BlahVar := "Blah"}}
{{template $BlahVar .}}
我要解决的总体问题是我需要根据配置文件有条件地渲染模板 - 所以我不知道模板的名称提前。显然,我可以在 FuncMap 中放置一个函数,它只进行单独的模板解析和调用并返回该结果,但想知道是否有更好的方法。
我看不到使用动态名称调用模板(文本或 html)的方法。例子:
这有效:
{{template "Blah" .}}
此错误与“模板调用中的意外“$BlahVar””:
{{$BlahVar := "Blah"}}
{{template $BlahVar .}}
我要解决的总体问题是我需要根据配置文件有条件地渲染模板 - 所以我不知道模板的名称提前。显然,我可以在 FuncMap 中放置一个函数,它只进行单独的模板解析和调用并返回该结果,但想知道是否有更好的方法。
作为对此的说明和跟进,我最终对这个问题有两个主要答案:1)尽量避免这种情况。在某些情况下,一个简单的 if 语句可以正常工作。2) 我能够使用 FuncMap 中的一个函数来完成此操作,该函数只是进行单独的渲染。这不是世界上最伟大的事情,但它确实有效并解决了问题。这是一个完整的独立演示,展示了这个想法:
package main
import (
"bytes"
"html/template"
"os"
)
func main() {
var err error
// our main template here calls a sub template
tpl := template.New("main")
// provide a func in the FuncMap which can access tpl to be able to look up templates
tpl.Funcs(map[string]interface{}{
"CallTemplate": func(name string, data interface{}) (ret template.HTML, err error) {
buf := bytes.NewBuffer([]byte{})
err = tpl.ExecuteTemplate(buf, name, data)
ret = template.HTML(buf.String())
return
},
})
// this is the main template
_, err = tpl.Parse(`
{{$Name := "examplesubtpl"}}
from main template
{{CallTemplate $Name .}}
`)
if err != nil {
panic(err)
}
// whatever code to dynamically figure out what templates to load
// a stub just to demonstrate
_, err = tpl.New("examplesubtpl").Parse(`
this is from examplesubtpl - see, it worked!
`)
if err != nil {
panic(err)
}
err = tpl.Execute(os.Stdout, map[string]interface{}{})
if err != nil {
panic(err)
}
}
另一种方法(虽然可能不是更好的方法)是拥有单独的模板文件,它们都提供相同的命名模板。例如,假设您有一个网页的共享布局:
<html>
...
<body>
{{template "body" .}}
</body>
</html>
在每个页面中,您都这样做:
{{define "body"}}
This will be in the body
{{end}}
然后将它们合并到代码中:
func compileTemplate(layout, name string) (*template.Template, error) {
tpl := template.New(name)
tpl, err := tpl.ParseFiles(
"views/layouts/"+layout+".htm",
"views/"+name+".htm",
)
if err != nil {
return nil, err
}
return tpl, nil
}
与我一起工作的才华横溢的开发人员设想的另一种方法是对模板实例进行后处理,以查找任何未定义的模板,并在文件系统上查找匹配文件并为找到的每个文件进行解析;然后渲染。
这为您提供了如下设置:
意见/index.html:
{{template "/includes/page-wrapper.html" .}}
{{define "body"}}
<div>Page guts go here</div>
{{end}}
{{define "head_section"}}
<title>Title Tag</title>
{{end}}
包括/page-wrapper.html:
<html>
<head>
{{block "head_section" .}}{{end}}
<head>
<body>
{{template "body" .}}
</body>
</html>
您的ServeHTTP()
方法在“views”目录中查找文件,加载并解析它,然后调用TmplIncludeAll()
(如下)。
我最终将相同的基本概念改编为几个函数,如下所示。 t
是解析后渲染前的模板。并且fs
是“views”和“includes”所在的目录(如上所述)。
func TmplIncludeAll(fs http.FileSystem, t *template.Template) error {
tlist := t.Templates()
for _, et := range tlist {
if et != nil && et.Tree != nil && et.Tree.Root != nil {
err := TmplIncludeNode(fs, et, et.Tree.Root)
if err != nil {
return err
}
}
}
return nil
}
func TmplIncludeNode(fs http.FileSystem, t *template.Template, node parse.Node) error {
if node == nil {
return nil
}
switch node := node.(type) {
case *parse.TemplateNode:
if node == nil {
return nil
}
// if template is already defined, do nothing
tlist := t.Templates()
for _, et := range tlist {
if node.Name == et.Name() {
return nil
}
}
t2 := t.New(node.Name)
f, err := fs.Open(node.Name)
if err != nil {
return err
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
return err
}
_, err = t2.Parse(string(b))
if err != nil {
return err
}
// start over again, will stop recursing when there are no more templates to include
return TmplIncludeAll(fs, t)
case *parse.ListNode:
if node == nil {
return nil
}
for _, node := range node.Nodes {
err := TmplIncludeNode(fs, t, node)
if err != nil {
return err
}
}
case *parse.IfNode:
if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
return err
}
if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
return err
}
case *parse.RangeNode:
if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
return err
}
if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
return err
}
case *parse.WithNode:
if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
return err
}
if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
return err
}
}
return nil
}
这是我最喜欢的方法,我已经使用了一段时间了。它的优点是只有一个模板渲染,错误消息很好而且干净,Go 模板标记非常易读和明显。如果 html/template.Template 的胆量使它更易于实现,那就太好了,但总体而言,它是 IMO 的出色解决方案。
使用htmltemplate.HTML()将解析后的模板("email/test") 注入另一个模板("email/main")
htmlTplEngine := htmltemplate.New("htmlTplEngine")
_, htmlTplEngineErr := htmlTplEngine.ParseGlob("views/email/*.html")
if nil != htmlTplEngineErr {
log.Panic(htmlTplEngineErr.Error())
}
var contentBuffer bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(&contentBuffer, "email/test", params); err != nil {
return "", "", errors.Wrap(err, "execute content html")
}
var templateBuf bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(
&templateBuf,
"email/main",
map[string]interface{}{
"Content": htmltemplate.HTML(contentBuffer.String()),
"Lang": language,
},
); err != nil {
return "", "", errors.Wrap(err, "execute html template")
}
在“电子邮件/主要”上
{{define "email/main"}}
My email/test template: {{.Content}}
{{end}}
使用杜松子酒时遇到同样的问题,最简单的解决方案是:
router.GET("/myurl", func(ctx *gin.Context) {
/*
--- do anything here --
*/
template1 := "template1.html"
template2 := "template2.html"
ctx.HTML(200, template1, nil)
ctx.HTML(200, template1, nil)
})
基本上,我将 html 内容拆分为单独的文件,并单独调用它们。只要响应码相同(例如:200),就不会触发任何问题。