Call other templates with a dynamic name

I do not see a way to call templates (text or html) with a dynamic name. Example:

It works:

{{template "Blah" .}} 

These errors with the "unexpected" $ BlahVar "in the template call":

 {{$BlahVar := "Blah"}} {{template $BlahVar .}} 

The general problem I'm trying to solve is that I need to visualize templates conditionally based on the configuration file, so I do not know the names of the templates ahead of time. Obviously, I can put a function in FuncMap that just does a separate parsing and call and returns the result, but wondered if there is a better way.

+6
source share
3 answers

Another approach that the talented developer I worked with is to process the Template instance to find any template that is not defined, and look at the file system for the corresponding file and analyze it for each one found; and then visualize after.

This gives you the following setup:

view / 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 ServeHTTP() for files in the "views" directory, loads and parses it, and then calls TmplIncludeAll() (below).

I ended up adapting the same basic concept as several functions, which are as follows. t is a template after analysis, but before rendering. And fs is the directory in which β€œviews” and β€œincludes” live (mentioned 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 have been using it for a while. The advantage is that there is only one render of the template, the error messages are nice and clean, and the layout of the Go template is very readable and obvious. It would be great if the gut html / template.Template made it easier to implement, but overall this is a great IMO solution.

0
source

As a note to this and subsequent observation, I ended up with two main answers to this question: 1) Try to avoid this. In some cases, a simple if statement worked fine. 2) I was able to accomplish this using a function in FuncMap that just does a separate rendering. This is not the best thing in the world, but it works and solves the problem. Here is a complete separate 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) } } 
+7
source

Another way, although perhaps not the best way, would be to have separate template files that provide the same template. For example, suppose you have a general layout for a web page:

 <html> ... <body> {{template "body" .}} </body> </html> 

On each page you do the following:

 {{define "body"}} This will be in the body {{end}} 

And then combine them into code:

 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 } 
+6
source

All Articles