英文:
Call other templates with dynamic name
问题
我看不到一种以动态名称调用模板(文本或HTML)的方法。例如:
这个可以工作:
{{template "Blah" .}}
这个会出现错误“在模板调用中出现意外的"$BlahVar"”:
{{$BlahVar := "Blah"}}
{{template $BlahVar .}}
我试图解决的整体问题是,我需要根据配置文件有条件地渲染模板 - 所以我事先不知道模板的名称。显然,我可以在FuncMap中放置一个函数,该函数只是进行单独的模板解析和调用,并返回该结果,但我想知道是否有更好的方法。
英文:
I do not see a way to call templates (text or html) with a dynamic name. Example:
This works:
{{template "Blah" .}}
This errors with "unexpected "$BlahVar" in template invocation":
{{$BlahVar := "Blah"}}
{{template $BlahVar .}}
The overall problem I'm trying to solve is that I need to render templates conditionally based on a configuration file - so I don't know the names of the templates ahead of time. Obviously I can put a function in the FuncMap which just does a separate template parsing and invocation and returns that result but was wondering if there is a Better Way.
答案1
得分: 22
关于这个问题,我最终得出了两个主要答案:1)尽量避免这种情况。在几种情况下,一个简单的if语句就可以解决问题。2)我通过在FuncMap中使用一个函数来实现这个目标,该函数只是进行了单独的渲染。虽然不是最好的解决方案,但它确实可以工作并解决问题。以下是一个完整的独立演示,展示了这个思路:
package main
import (
"bytes"
"html/template"
"os"
)
func main() {
var err error
// 我们的主模板在这里调用一个子模板
tpl := template.New("main")
// 在FuncMap中提供一个可以访问tpl的函数,以便能够查找模板
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
},
})
// 这是主模板
_, err = tpl.Parse(`
{{$Name := "examplesubtpl"}}
来自主模板
{{CallTemplate $Name .}}
`)
if err != nil {
panic(err)
}
// 动态确定要加载的模板的任何代码
// 一个示例模板的存根
_, err = tpl.New("examplesubtpl").Parse(`
这是来自examplesubtpl的内容 - 看,它起作用了!
`)
if err != nil {
panic(err)
}
err = tpl.Execute(os.Stdout, map[string]interface{}{})
if err != nil {
panic(err)
}
}
希望对你有帮助!
英文:
As a note on this and to follow up, I eventually ended up with two main answers to this question: 1) Try to avoid this. In several cases a simple if statement worked fine. 2) I was able to accomplish this using a function in the FuncMap that just does a separate rendering. It's not the greatest thing in the world, but it does work and solves the problem. Here is a full standalone demo that shows the idea:
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)
}
}
答案2
得分: 10
另一种方式,虽然可能不是更好的方式,是使用单独的模板文件,它们都提供相同的命名模板。例如,假设你有一个用于网页的共享布局:
<html>
...
<body>
{{template "body" .}}
</body>
</html>
在每个页面中,你可以这样做:
{{define "body"}}
这将在 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
}
英文:
Another way, though perhaps not a better way, would be to have separate template files which all provide the same named template. For example suppose you have a shared layout for a web page:
<!-- language: html -->
<html>
...
<body>
{{template "body" .}}
</body>
</html>
In each page you do this:
<!-- language: html -->
{{define "body"}}
This will be in the body
{{end}}
And then merge them in code:
<!-- language: go -->
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
}
答案3
得分: 4
我与一位才华横溢的开发人员合作时,他提出了一种不同的方法,即对模板实例进行后处理,查找未定义的模板包含,并在文件系统中查找匹配的文件并解析每个找到的文件;然后进行渲染。
这样可以得到以下设置:
views/index.html:
{{template "/includes/page-wrapper.html" .}}
{{define "body"}}
<div>页面内容放在这里</div>
{{end}}
{{define "head_section"}}
<title>标题标签</title>
{{end}}
includes/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
}
// 如果模板已经定义,不做任何操作
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
}
// 重新开始,当没有更多要包含的模板时停止递归
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 的内部实现更简单一些,实现起来可能更简单,但总体上它是一个很好的解决方案。
英文:
A different approach that a talented dev I worked with dreamed up was to post-process the Template instance to find any template includes which are not defined and look on the filesystem for a matching file and parse it for each one found; and then render after.
This gives you a setup like follows:
views/index.html:
{{template "/includes/page-wrapper.html" .}}
{{define "body"}}
<div>Page guts go here</div>
{{end}}
{{define "head_section"}}
<title>Title Tag</title>
{{end}}
includes/page-wrapper.html:
<html>
<head>
{{block "head_section" .}}{{end}}
<head>
<body>
{{template "body" .}}
</body>
</html>
And your ServeHTTP()
method looks for files in the "views" directory, loads and parses it and then calls TmplIncludeAll()
(below).
I ended up adapting this same basic concept as just a couple of functions, which are as follows. t
is the template after being parsed but before rendering. And fs
is the directory where "views" and "includes" live (referred to above).
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
}
This is my favorite approach and I've been using this for a while now. It has the advantage that there is only one template render, the error messages are nice and clean and the Go template markup is very readable and obvious. It would be great if the guts of html/template.Template made this simpler to implement, but it overall is an excellent solution IMO.
答案4
得分: 2
使用**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")
}
在"email/main"模板中:
{{define "email/main"}}
我的email/test模板: {{.Content}}
{{end}}
英文:
Using htmltemplate.HTML() to inject parsed template("email/test") on another template("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")
}
On "email/main"
{{define "email/main"}}
My email/test template: {{.Content}}
{{end}}
答案5
得分: 0
在使用gin时遇到了相同的问题,最简单的解决方案是:
router.GET("/myurl", func(ctx *gin.Context) {
/*
--- 在这里做任何操作 ---
*/
template1 := "template1.html"
template2 := "template2.html"
ctx.HTML(200, template1, nil)
ctx.HTML(200, template2, nil)
})
基本上,我将HTML内容拆分为单独的文件,并逐个调用它们。只要响应代码相同(例如:200),就不会触发任何问题。
英文:
Faced the same issue while using gin, and simplest solution was:
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)
})
Basically I split the html content into separated files, and call them up individually. As long as the response code is the same (for example: 200), then it won't be triggering any issue.
答案6
得分: 0
如果模板变量针对已知的一组备选项进行选择,那么我最终会这样做,要么手动选择,要么自动生成后缀来选择模板。
{{define "foo "}}
英文版本
{{end}}
{{define "foo [cy]"}}
威尔士语版本
{{end}}
{{define "foo"}}
{{if eq .Lang "en"}}
{{template "foo "}}
{{else if eq .Lang "cy"}}
{{template "foo [cy]"}}
{{else}}
{{error "缺少或无效的 .Lang"}}
{{end}}
{{end}}
我在其他地方使用的另一种方法是,在执行模板之前,解析一个小的动态生成的模板,如下所示:
clone, _ := rootTemplate.Clone() // 省略了错误检查
clone.Parse(`{{define "body"}}{{template "`+name+`" .}}{{end}}`)
clone.ExecuteTemplate(...)
英文:
If the template variable selects against a known set of alternatives, then I ended up doing this, either manually or automatically generating a suffix to select between the templates.
{{define "foo "}}
English version
{{end}}
{{define "foo [cy]"}}
Welsh version
{{end}}
{{define "foo"}}
{{if eq .Lang "en"}}
{{template "foo "}}
{{else if eq .Lang "cy"}}
{{template "foo [cy]"}}
{{else}}
{{error "missing or invalid .Lang"}}
{{end}}
{{end}}
Another approach I'm using elsewhere is to, just prior to executing a template, parsing a small dynamically generated template like so:
clone, _ := rootTemplate.Clone() // error checking elided
clone.Parse(`{{define "body"}}{{template "`+name+`" .}}{{end}}`)
clone.ExectueTemplate(...)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论