Tuesday, November 15, 2011

Go Templates - Part 3 - Template Sets


A template Set WAS a specific data type that allowed you to group together related templates in one group. Though it does not exist now as an individual data type, it is subsumed within the Template data structure. So if you now parsed a set of files that contains text within it separated by {{define}}...{{end}} sections, each of them become a template.

For example if a web page had a header, body, and footer, these could be defined within one text file. This could then could be read into your program with one call which will parse all the template definitions. For example, assume the text as shown below which illustrates template definitions within a single file.

Incomplete sample template set file
{{define "header"}}
<html>
<head></head>
{{end}}

{{define "body"}}
<body>
</body>
{{end}}

{{define "footer"}}
</html>
{{end}}


Key points to note:
* each template is enclosed within {{define}} and {{end}}
* each template is given a unique name - repeating the same name will cause a panic
* text outside of a {{define}} block is not allowed - it will cause a panic
* white space within the the {{define}} block is taken as is - in the above example there is a new line character before the first html tag and also after the closing head tag.

When this particular file is parsed in as a set, it creates a map of template names to parsed templates. So the above could be represented for illustration as):
tmplVar["header"] = pointer to parsed template of text "<html> … </head>"
tmplVar["body"] = pointer to parsed template of text "<body> … </body>"
tmplVar["footer"] = pointer to parsed template of text "</html>"


Templates within a set know about each other. If the same template needs to be used by different sets, it needs to be parsed separately. If the "footer" above can be repeated, keep it as a separate file and have each set parse it separately.


We shall now do a simple example. In this example, we shall learn how to
* define a template - using {{define}}
* include one template within another - using {{template "template name"}}
* parse a bunch of files and create a set using template.ParseFiles
* execute/merge contents of the templates using template.ExecuteTemplate
* and finally, how to add external templates to an existing set using template.Set.Add
(I don't think there is a way to do this anymore, but if somebody knows, please let me know too.)

Full template file - t1.tmpl
{{define "t_ab"}}a b{{template "t_cd"}}e f {{end}}

The file above will be parsed in as a template named "t_ab". It has, within it, "a b /missing/ e f", but is missing a couple of letters in the alphabet. For that it intends to include another template called "t_cd" (which should be in the same set).

Full template file - t2.tmpl
{{define "t_cd"}} c d {{end}}

The file above will be parsed in as a template called "t_cd".

Full program
package main

import (
    "text/template"
    "os"
    "fmt"
    )

func main() {
    fmt.Println("Load a set of templates with {{define}} clauses and execute:")
    s1, _ := template.ParseFiles("t1.tmpl", "t2.tmpl") //create a set of templates from many files.
    //Note that t1.tmpl is the file with contents "{{define "t_ab"}}a b{{template "t_cd"}}e f {{end}}"
    //Note that t2.tmpl is the file with contents "{{define "t_cd"}} c d {{end}}"

s1.ExecuteTemplate(os.Stdout, "t_cd", nil) //just printing of c d fmt.Println() s1.ExecuteTemplate(os.Stdout, "t_ab", nil) //execute t_ab which will include t_cd fmt.Println() s1.Execute(os.Stdout, nil) //since templates in this data structure are named, there is no default template and so it prints nothing }


In the first part, our code first parses the files "t1.tmpl" and "t2.tmpl" in the local directory with template.ParseFiles. (If you have other files named similar to t*.tmpl in the local directory, there could be a panic as it might not be a properly parseable template file.) Each {{define}} {{end}} block becomes a separate template within the same group of templates. In our case there is only one template per file - "t_ab" from t1.tmpl and "t_cd" from t2.tmpl. We first execute the smaller template, "t_cd", to show its contents. When we execute the outer template "t_ab", it also picks up the parsed value of template "t_cd" (because you have asked for it to be included with the template clause). Finally, we simply do an Execute on the template for the purpose of illustrating to ourselves that there is no default template - that statement prints nothing.

c d
a b c d e f
-empty-




The function template.ParseGlob is similar to template.ParseFiles, except that it now takes a wildcard string for the filenames. In the above example, you could substitute it with template.ParseGlob("t*.tmpl") and still get the same output.


5 comments:

  1. How to add a template to an existing template using template.AddParseTree:

    tparent, _ := template.New("parent").Parse(
    "<i>{{template \"child\" .}}</i>")
    tchild, _ := template.New("child").Parse(
    "<b>{{.lang}}</b>")
    ts, _ = tparent.AddParseTree("child", tchild.Tree)
    ts.ExecuteTemplate(os.Stdout, "parent", map[string]string{"lang": "Go"})

    ReplyDelete
    Replies
    1. The code you provided is not working. They results with the below error:

      tchild.Tree undefined (type *"html/template".Template has no field or method Tree)

      Delete
    2. Oh. "html/template" is different from "text/template" and it does not have a ".Tree" attribute.

      Delete
  2. Can you please explain how these 2 statements are different?

    1) s1, _ :=

    2) s1 :=

    ReplyDelete
    Replies
    1. 1) returns two values the "_" means we don't care about the second only the first which is assigned to "s1".

      I'm sure you can work out what 2) means

      Delete

If you think others also will find these tutorials useful, kindly "+1" it above and mention the link in your own blogs, responses, and entries on the net so that others also may reach here. Thank you.

Note: Only a member of this blog may post a comment.