#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 infomration.
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 replaceParseFiles
becauseParse
can test content from string, so we don't need extra files. - Use
main
for every example and do not usehandler
. - Use
os.Stdout
to replacehttp.ResponseWriter
becauseos.Stdout
also implemented interfaceio.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 towith
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