This repository has been archived by the owner. It is now read-only.
Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
368 lines (268 sloc) 11 KB

7.4 Templates

What is template?

I believe you've heard MVC design model, where Model processes data, View shows results, Controller handles user requests. As for View level. Many dynamic languages generate data by writing code in static HTML files, like JSP implements by inserting <%=....=%>, PHP implements by inserting <?php.....?>.

The following shows template mechanism:

Figure 7.1 Template mechanism

Most of content that web applications response to clients is static, and dynamic part is usually small. For example, you need to show a list of visited users, only user name is dynamic, and style of list is always the same, so template is for reusing static content.

Template in Go

In Go, we have package template to handle templates, and use functions like Parse, ParseFile, Execute to load templates from text or files, then execute merge like figure 7.1.

Example:

func handler(w http.ResponseWriter, r *http.Request) {
	t := template.New("some template") // Create a template.
	t, _ = t.ParseFiles("tmpl/welcome.html", nil)  // Parse template file.
	user := GetUser() // Get current user information.
	t.Execute(w, user)  // merge.
}

As you can see, it's very easy to use template in Go, load and render data, just like in other programming languages.

For convenient purpose, we use following rules in examples:

  • Use Parse to replace ParseFiles because Parse can test content from string, so we don't need extra files.
  • Use main for every example and do not use handler.
  • Use os.Stdout to replace http.ResponseWriter because os.Stdout also implemented interface io.Writer.

Insert data to template

We showed you how to parse and render templates above, let's take one step more to render data to templates. Every template is an object in Go, so how to insert fields to templates?

Fields

Every field that is going to be rendered in templates in Go should be put inside of {{}}, {{.}} is shorthand for current object, it's similar to Java or C++. If you want to access fields of current object, you should use {{.FieldName}}. Notice that only exported fields can be accessed in templates. Here is an example:

package main

import (
	"html/template"
	"os"
)

type Person struct {
	UserName string
}

func main() {
	t := template.New("fieldname example")
	t, _ = t.Parse("hello {{.UserName}}!")
	p := Person{UserName: "Astaxie"}
	t.Execute(os.Stdout, p)
}

The above example outputs hello Astaxie correctly, but if we modify a little bit, the error comes out:

type Person struct {
	UserName string
	email	string  // Field is not exported.
}

t, _ = t.Parse("hello {{.UserName}}! {{.email}}")

This part of code will not be compiled because we try to access a field that is not exported; however, if we try to use a field that does not exist, Go simply outputs empty string instead of error.

If you print {{.}} in templates, Go outputs formatted string of this object, it calls fmt underlying.

Nested fields

We know how to output a field now, what if the field is an object, and it also has its fields, how to print them all in loop? We can use {{with …}}…{{end}} and {{range …}}{{end}} to do this job.

  • {{range}} just like range in Go.
  • {{with}} lets you write same object name once, and use . as shorthand( Similar to with in VB ).

More examples:

package main

import (
	"html/template"
	"os"
)

type Friend struct {
	Fname string
}

type Person struct {
	UserName string
	Emails   []string
	Friends  []*Friend
}

func main() {
	f1 := Friend{Fname: "minux.ma"}
	f2 := Friend{Fname: "xushiwei"}
	t := template.New("fieldname example")
	t, _ = t.Parse(`hello {{.UserName}}!
			{{range .Emails}}
				an email {{.}}
			{{end}}
			{{with .Friends}}
			{{range .}}
				my friend name is {{.Fname}}
			{{end}}
			{{end}}
			`)
	p := Person{UserName: "Astaxie",
		Emails:  []string{"astaxie@beego.me", "astaxie@gmail.com"},
		Friends: []*Friend{&f1, &f2}}
	t.Execute(os.Stdout, p)
}

Condition

If you need to check conditions in templates, you can use syntax if-else just like you use it in Go programs. If pipeline is empty, default value of if is false. Following example shows how to use if-else in templates:

package main

import (
	"os"
	"text/template"
)

func main() {
	tEmpty := template.New("template test")
	tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n"))
	tEmpty.Execute(os.Stdout, nil)

	tWithValue := template.New("template test")
	tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n"))
	tWithValue.Execute(os.Stdout, nil)

	tIfElse := template.New("template test")
	tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n"))
	tIfElse.Execute(os.Stdout, nil)
}

As you can see, it's easy to use if-else in you tempaltes.

** Attention ** You CANNOT use conditional expression in if, like .Mail=="astaxie@gmail.com", only boolean value is acceptable.

pipelines

Unix users should be familiar with pipe like ls | grep "beego", this command filter files and only show them that contains beego. One thing I like Go template is that it support pipe, anything in {{}} can be data of pipelines. The e-mail we used above can cause XSS attack, so how can we fix this through pipe?

{{. | html}}

We can use this way to escape e-mail body to HTML, it's quite same as we write Unix commands and convenient for using template functions.

Template variable

Sometimes we need to use local variables in templates, and we can use them with with``range``if, and its scope is between these keywords and {{end}}. Declare local variable example:

$variable := pipeline

More examples:

{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}

Template function

Go uses package fmt to format output in templates, but sometimes we need to do something else. For example, we want to replace @ with at in our e-mail address like astaxie at beego.me. At this point, we have to write customized function.

Every template function has unique name and associates with one function in your Go programs as follows:

type FuncMap map[string]interface{}

Suppose we have template function emailDeal and it associates with EmailDealWith in Go programs, then we use following code to register this function:

t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})

EmailDealWith definition:

func EmailDealWith(args …interface{}) string

Example:

package main

import (
	"fmt"
	"html/template"
	"os"
	"strings"
)

type Friend struct {
	Fname string
}

type Person struct {
	UserName string
	Emails   []string
	Friends  []*Friend
}

func EmailDealWith(args ...interface{}) string {
	ok := false
	var s string
	if len(args) == 1 {
		s, ok = args[0].(string)
	}
	if !ok {
		s = fmt.Sprint(args...)
	}
	// find the @ symbol
	substrs := strings.Split(s, "@")
	if len(substrs) != 2 {
		return s
	}
	// replace the @ by " at "
	return (substrs[0] + " at " + substrs[1])
}

func main() {
	f1 := Friend{Fname: "minux.ma"}
	f2 := Friend{Fname: "xushiwei"}
	t := template.New("fieldname example")
	t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith})
	t, _ = t.Parse(`hello {{.UserName}}!
				{{range .Emails}}
					an emails {{.|emailDeal}}
				{{end}}
				{{with .Friends}}
				{{range .}}
					my friend name is {{.Fname}}
				{{end}}
				{{end}}
				`)
	p := Person{UserName: "Astaxie",
		Emails:  []string{"astaxie@beego.me", "astaxie@gmail.com"},
		Friends: []*Friend{&f1, &f2}}
	t.Execute(os.Stdout, p)
}

Here is a list of built-in template functions:

var builtins = FuncMap{
	"and":      and,
	"call":     call,
	"html":     HTMLEscaper,
	"index":    index,
	"js":       JSEscaper,
	"len":      length,
	"not":      not,
	"or":       or,
	"print":    fmt.Sprint,
	"printf":   fmt.Sprintf,
	"println":  fmt.Sprintln,
	"urlquery": URLQueryEscaper,
}

Must

In package template has a function Must which is for checking template validation, like matching of braces, comments, variables. Let's give an example of Must:

package main

import (
	"fmt"
	"text/template"
)

func main() {
	tOk := template.New("first")
	template.Must(tOk.Parse(" some static text /* and a comment */"))
	fmt.Println("The first one parsed OK.")

	template.Must(template.New("second").Parse("some static text {{ .Name }}"))
	fmt.Println("The second one parsed OK.")

	fmt.Println("The next one ought to fail.")
	tErr := template.New("check parse error with Must")
	template.Must(tErr.Parse(" some static text {{ .Name }"))
}

Output:

The first one parsed OK.
The second one parsed OK.
The next one ought to fail.
panic: template: check parse error with Must:1: unexpected "}" in command

Nested templates

Like we write code, some part of template is the same in several templates, like header and footer of a blog, so we can define header, content and footer these 3 parts. Go uses following syntax to declare sub-template:

{{define "sub-template"}}content{{end}}

Call by following syntax:

{{template "sub-template"}}

A complete example, suppose we have header.tmpl, content.tmpl, footer.tmpl` these 3 files.

Main template:

//header.tmpl
{{define "header"}}
<html>
<head>
	<title>Something here</title>
</head>
<body>
{{end}}

//content.tmpl
{{define "content"}}
{{template "header"}}
<h1>Nested here</h1>
<ul>
	<li>Nested usag</li>
	<li>Call template</li>
</ul>
{{template "footer"}}
{{end}}

//footer.tmpl
{{define "footer"}}
</body>
</html>
{{end}}

Code:

package main

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

func main() {
	s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl")
	s1.ExecuteTemplate(os.Stdout, "header", nil)
	fmt.Println()
	s1.ExecuteTemplate(os.Stdout, "content", nil)
	fmt.Println()
	s1.ExecuteTemplate(os.Stdout, "footer", nil)
	fmt.Println()
	s1.Execute(os.Stdout, nil)
}

We can see that template.ParseFiles parses all nested templates into cache, and every template that is defined by {{define}} is independent, they are paralleled in something like map, where key is template name and value is body of template. Then we use ExecuteTemplate to execute corresponding sub-template, so that header and footer are independent and content has both of them. But if we try to execute s1.Execute, nothing will be outputted because there is no default sub-template available.

Templates in one set knows each other, but you have to parse them for every single set.

Summary

In this section, you learned that how to combine dynamic data with templates, including print data in loop, template functions, nested templates, etc. By using templates, we can finish V part of MVC model. In following chapters, we will cover M and C parts.

Links