Hopefully you're aware of the MVC (Model, View, Controller) design model, where models process data, views show the results and finally, controllers handle user requests. For views, many dynamic languages generate data by writing code in static HTML files. For instance, JSP is implemented by inserting <%=....=%>
, PHP by inserting <?php.....?>
, etc.
The following demonstrates the template mechanism:
Figure 7.1 Template mechanism
Most of the content that web applications respond to clients with is static, and the dynamic parts are usually very small. For example, if you need to show a list users who have visited a page, only the user name would be dynamic. The style of the list remains the same. As you can see, templates are useful for reusing static content.
In Go, we have the template
package to help handle templates. We can use functions like Parse
, ParseFile
and Execute
to load templates from plain text or files, then evaluate the dynamic parts, like in 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, load and render data in templates in Go, just like in other programming languages.
For the sake of convenience, we will use the following rules in our examples:
- Use
Parse
to replaceParseFiles
becauseParse
can test content directly from strings, so we don't need any extra files. - Use
main
for every example and do not usehandler
. - Use
os.Stdout
to replacehttp.ResponseWriter
sinceos.Stdout
also implements theio.Writer
interface.
We've just showed you how to parse and render templates. Let's take it one step further and render data to our templates. Every template is an object in Go, so how do we insert fields to templates?
In Go, Every field that you intend to be rendered within a template should be put inside of {{}}
. {{.}}
is shorthand for the current object, which is similar to its Java or C++ counterpart. If you want to access the fields of the 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 our struct a little bit, the following error emerges:
type Person struct {
UserName string
email string // Field is not exported.
}
t, _ = t.Parse("hello {{.UserName}}! {{.email}}")
This part of the code will not be compiled because we try to access a field that has not been exported. However, if we try to use a field that does not exist, Go simply outputs an empty string instead of an error.
If you print {{.}}
in a template, Go outputs formatted string of this object, calling fmt
under the covers.
We know how to output a field now. What if the field is an object, and it also has its own fields? How do we print them all in one loop? We can use {{with …}}…{{end}}
and {{range …}}{{end}}
for exactly that.
{{range}}
just like range in Go.{{with}}
lets you write the same object name once and use.
as shorthand for it ( 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{"[email protected]", "[email protected]"},
Friends: []*Friend{&f1, &f2}}
t.Execute(os.Stdout, p)
}
If you need to check for conditions in templates, you can use the if-else
syntax just like you do in regular Go programs. If the pipeline is empty, the default value of if
is false
. The 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 templates.
** Attention ** You CANNOT use conditional expressions in if, for instance .Mail=="[email protected]"
. Only boolean values are acceptable.
Unix users should be familiar with the pipe
operator, like ls | grep "beego"
. This command filters files and only shows those that contain the word beego
. One thing that I like about Go templates is that they support pipes. Anything in {{}}
can be the data of pipelines. The e-mail we used above can render our application vulnerable to XSS attacks. How can we address this issue using pipes?
{{. | html}}
We can use this method to escape the e-mail body to HTML. It's quite the same as writing a Unix command, and its convenient for use in template functions.
Sometimes we need to use local variables in templates. We can use them with the with
, range
and if
keywords, and their scope is between these keywords and {{end}}
. Here's an example of declaring a global variable:
$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}}
Go uses the fmt
package to format output in templates, but sometimes we need to do something else. As an example scenario, let's say we want to replace @
with at
in our e-mail address, like astaxie at beego.me
. At this point, we have to write a customized function.
Every template function has a unique name and is associated with one function in your Go program as follows:
type FuncMap map[string]interface{}
Suppose we have an emailDeal
template function associated with its EmailDealWith
counterpart function in our Go program. We can use the 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{"[email protected]", "[email protected]"},
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,
}
The template package has a function called Must
which is for validating templates, like the matching of braces, comments, and variables. Let's take a look at 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
Just like in most web applications, certain parts of templates can be reused across other templates, like the headers and footers of a blog. We can declare header
, content
and footer
as sub-templates, and declare them in Go using the following syntax:
{{define "sub-template"}}content{{end}}
The sub-template is called using the following syntax:
{{template "sub-template"}}
Here's a complete example, supposing that we have the following three files: header.tmpl
, content.tmpl
and footer.tmpl
.
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)
}
Here we can see that template.ParseFiles
parses all nested templates into cache, and that every template defined by {{define}}
is independent of one another. They are persisted in something like a map, where the template names are keys and the values are the template bodies. We can then use ExecuteTemplate
to execute the corresponding sub-templates, so that the header and footer are independent and content contains them both. Note that if we try to execute s1.Execute
, nothing will be outputted because there is no default sub-template available.
Templates in one set know each other, but you must parse them for every single set.
In this section, you learned how to combine dynamic data with templates using techniques including printing data in loops, template functions and nested templates. By learning about templates, we can conclude discussing the V part of the MVC architecture. In the following chapters, we will cover the M and C aspects of MVC.