go提供了text/template库和 html/template库这两个模板引擎,模板引擎通过将数据和模板组合在一起生成最终的HTML,而处理器负责调用模板引擎并将引擎生成的HTML返回给客户端
go的模板都是文本文档(其中web应用的模板通常都是HTML),它们都嵌入了一些称为动作的指令。从模板引擎的角度来说,模板就是嵌入了动作的文本(这些文本通常包含在模板文件里面),而模板引擎则通过分析并执行这些文本来生成出另外一些文本
HelloWorld
使用go的web模板引擎需要以下两个步骤:
- 对文本格式的模板源进行语法分析,创建一个经过语法分析的模板结构,其中模板源既可以是一个字符串,也可以是模板文件中包含的内容
- 执行经过语法分析的模板,将ResponseWriter 和模板所需的动态数据传递给模板引擎,被调用的模板引擎会把经过语法分析的模板和传入的数据结合起来,生成出最终的HTML,并将这些HTML传递给 ResponseWriter
- 创建模板文件 index.html
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
我是模板文件中的内容<br>
后台传过来的数据是: {{.}}
</body>
</html>
- 在处理器中触发模板引擎
package main
import (
"html/template"
"net/http"
)
//创建处理器函数
func testTemplate(w http.ResponseWriter, r *http.Request) {
//解析模板
t, _ := template.ParseFiles("index.html")
//执行
t.Execute(w, "Hello Template")
}
func main() {
http.HandleFunc("/testTemplate", testTemplate)
http.ListenAndServe(":8080", nil)
}
- 浏览器中的结果
我是模板文件中的内容
后台传过来的数据是: Hello Template
解析模板
- ParseFiles函数
func ParseFiles
func ParseFiles(filenames ...string) (*Template, error)
ParseFiles函数创建一个模板并解析filenames指定的文件里的模板定义。返回的模板的名字是第一个文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要提供一个文件。如果发生错误,会停止解析并返回nil
- 当调用ParseFiles函数解析模板文件时,go会创建一个新的模板,并将给定的模板文件的名字作为新模板的名字,如果该函数中传入了多个文件名,只会返回一个模板,而且以第一个文件的文件名作为模板的名字,至于其他文件对应的模板则会被放到一个map中
t, _ := template.ParseFiles("index.html")
- 以上代码相当于调用New函数创建一个新模板,然后再调用template的ParseFiles方法:
t, _ := template.New("index.html")
t, _ = t.ParseFiles("index.html")
func New
func New(name string) *Template
创建一个名为name的模板
func (*Template) ParseFiles
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
ParseGlob方法解析filenames指定的文件里的模板定义并将解析结果与t关联。如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要提供一个文件
- 在解析模板时都没有对错误进行处理,go提供了一个Must函数专门用来处理这个错误。Must函数可以包裹起一个函数,被包裹的函数会返回一个指向模板的指针和一个错误,如果错误不是nil,那么Must函数将产生一个panic
func Must
func Must(t *Template, err error) *Template
Must函数用于包装返回(*Template, error)的函数/方法调用,它会在err非nil时panic,一般用于变量初始化:
var t = template.Must(template.New("name").Parse("html"))
代码
t := template.Must(template.ParseFiles("index.html"))
- ParseGlob函数
func ParseGlob
func ParseGlob(pattern string) (*Template, error)
ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。如果发生错误,会停止解析并返回nil。ParseGlob等价于使用匹配pattern的文件的列表为参数调用ParseFiles
通过该函数可以通过指定一个规则一次性传入多个模板文件,如:
t, _ := template.ParseGlob("*.html")
执行模板
- 通过
func (*Template) Execute
func (t *Template) Execute(wr io.Writer, data interface{}) error
Execute方法将解析好的模板应用到data上,并将输出写入wr。如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行
- 如果只有一个模板文件,调用这个方法总是可行的;但是如果有多个模板文件,调用这个方法只能得到第一个模板
- 通过ExecuteTemplate方法
func (*Template) ExecuteTemplate
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error
ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出
- 如果直接调用Execute方法,则只有模板index.html会被执行,要执行模板index2.html , 则需要调用ExecuteTemplate 方法
index.html
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
我是模板文件中的内容<br>
后台传过来的数据是: {{.}}
</body>
</html>
index2.html
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
index2.html中的数据是: {{.}}
</body>
</html>
main.go
package main
import (
"html/template"
"net/http"
)
//创建处理器函数
func testTemplate(w http.ResponseWriter, r *http.Request) {
//解析模板
//t, _ := template.ParseFiles("index.html")
t := template.Must(template.ParseFiles("index.html", "index2.html"))
//执行
//t.Execute(w, "Hello Template")
//将响应数据在index2.html文件中显示
t.ExecuteTemplate(w, "index2.html", "我要去index2.html中")
}
func main() {
http.HandleFunc("/testTemplate", testTemplate)
http.ListenAndServe(":8080", nil)
}
动作
text/template 包
go模板的动作就是一些嵌入到模板里面的命令,这些命令在模板中需要放到两个大括号里 {{ 动作 }},
点
代表传递给模板的数据
{{.}}
条件动作
- 格式1:
{{if arg}}
要显示的内容
{{end}}
- 格式2:
{{if arg}}
要显示的内容
{{else}}
if条件不满足是要显示的内容
{{end}}
说明:其中的arg是传递给条件动作的参数,该值可以是一个字符串常量、一个变量、一个返回单个值的函数或者方法等
模板文件
if.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{if .}}
我要显示出来
{{end}}
<hr />
{{if .}}
如果传过来的是true将显示这部分内容
{{else}}
else中的内容被显示
{{end}}
</body>
</html>
处理器代码
package main
import (
"html/template"
"net/http"
)
func testIf(w http.ResponseWriter, r *http.Request) {
//解析模板
t := template.Must(template.ParseFiles("if.html"))
age := 17
//执行
t.Execute(w, age > 18)
}
func main() {
http.HandleFunc("/testIf", testIf)
http.ListenAndServe(":8080", nil)
}
浏览器结果
else中的内容被显示
迭代动作
迭代动作可以对数组、切片、映射或者管道进行迭代
- 格式1:
{{range .}}
遍历到的元素是 {{.}}
{{end}}
- 格式2:
{{range .}}
遍历到的元素是 {{.}}
{{else}}
没有任何元素
{{end}}
说明:
- range后面的点 代表被遍历的元素
- 要显示的内容里面的点 代表遍历到的元素
模板文件
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{range .}}
遍历到的元素:
{{.}}<br>
<a href="#">{{.LastName}}</a><br>
{{else}}
没有遍历到任何元素
{{end}}
</body>
</html>
处理器代码
employee.go
package model
type Employee struct {
ID int
LastName string
Email string
}
main.go
package main
import (
"goweb/web03_action/model"
"html/template"
"net/http"
)
//测试range
func testRange(w http.ResponseWriter, r *http.Request) {
//解析模板
t := template.Must(template.ParseFiles("range.html"))
var emps []*model.Employee
emp := &model.Employee{
ID: 1,
LastName: "zhangsan",
Email: "[email protected]",
}
emps = append(emps, emp)
emp2 := &model.Employee{
ID: 2,
LastName: "lisi",
Email: "[email protected]",
}
emps = append(emps, emp2)
emp3 := &model.Employee{
ID: 3,
LastName: "wangwu",
Email: "[email protected]",
}
emps = append(emps, emp3)
//执行
t.Execute(w, emps)
}
func main() {
http.HandleFunc("/testRange", testRange)
http.ListenAndServe(":8080", nil)
}
浏览器结果
遍历到的元素: {1 zhangsan [email protected]}
zhangsan
遍历到的元素: {2 lisi [email protected]}
lisi
遍历到的元素: {3 wangwu [email protected]}
wangwu
- 迭代之后是一个结构体,获取结构体中的字段值使用 .字段名
{{range .}}
获取结构体的Name字段名 {{.Name}}
{{end}}
- 迭代Map时可以设置变量,变量以$开头:
{{range $k, $v := .}}
键是 {{$k}} 值是 {{$v}}
{{end}}
- 迭代管道
{{c1|c2|c3}}
说明: c1 , c2 , 和 c3 可以是参数或者函数。管道允许用户将一个参数的输出传递给下一个参数,各个参数之间使用 | 分割
设置动作
设置动作允许在指定的范围内对点 (.) 设置值
- 格式1:
{{with arg}}
为传过来的数据设置的新值是 {{.}}
{{end}}
- 格式2:
{{with arg}}
为传过来的数据设置的新值是 {{.}}
{{else}}
传过来的数据仍然是 {{.}}
{{end}}
模板文件
with.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{with "太子"}}
<div>修改之后的数据是: {{.}}</div>
{{end}}
<hr/>
{{with ""}}
<div>修改之后的数据是: {{.}}</div>
{{else}}
<div>传过来的数据仍是: {{.}}</div>
{{end}}
</body>
</html>
处理器代码
package main
import (
"html/template"
"net/http"
)
//测试With
func testWith(w http.ResponseWriter, r *http.Request) {
//解析模板
t := template.Must(template.ParseFiles("with.html"))
//执行
t.Execute(w, "狸猫")
}
func main() {
http.HandleFunc("/testWith", testWith)
http.ListenAndServe(":8080", nil)
}
浏览器结果:
修改之后的数据是: 太子
传过来的数据仍是: 狸猫
包含动作
包含动作允许用户在一个模板里面包含另一个模板,从而构建出嵌套的模板
- 格式1:
{{template "name"}}
说明: name为被包含的模板的名字
- 格式2:
{{template "name" arg}}
说明: arg 是用户想要传递给被嵌套模板的数据
模板文件
template1.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>后台传过来的数据是: {{.}}</div>
<div>包含template2.html里面的内容</div>
{{template "template2.html"}}
<hr/>
<div>将template1.html中得到的数据传入到template2.html后</div>
{{template "template2.html" .}}
</body>
</html>
template2.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>template2.html里面传入的数据是: {{.}}</div>
</body>
</html>
处理器端代码
package main
import (
"html/template"
"net/http"
)
//测试template
func testTemplate(w http.ResponseWriter, r *http.Request) {
//解析模板
t := template.Must(template.ParseFiles("template1.html", "template2.html"))
//执行
t.Execute(w, "我能在两个文件中显示吗?")
}
func main() {
http.HandleFunc("/testTemplate", testTemplate)
http.ListenAndServe(":8080", nil)
}
浏览器结果:
后台传过来的数据是: 我能在两个文件中显示吗?
包含template2.html里面的内容
template2.html里面传入的数据是:
将template1.html中得到的数据传入到template2.html后
template2.html里面传入的数据是: 我能在两个文件中显示吗?
定义动作
当我们访问一些网站时,经常会看到好多网页中有相同的部分:比如导航栏、版权信息、联系方式等。这些相同的布局可以通过定义动作在模板文件中定义模板来实现
- 定义模板的格式:
{{define "layout"}}
...
{{end}}
- 在一个模板文件中定义多个模板
模板文件
define.html
<!-- 定义模板 -->
{{define "model"}}
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{template "content"}}
</body>
</html>
{{end}}
{{define "content"}}
<a href="https://www.baidu.com">百度</a>
{{end}}
处理器代码
package main
import (
"html/template"
"net/http"
)
//测试define
func testDefine(w http.ResponseWriter, r *http.Request) {
//解析模板
t := template.Must(template.ParseFiles("define.html"))
//执行
t.ExecuteTemplate(w, "model", "")
}
func main() {
http.HandleFunc("/testDefine", testDefine)
http.ListenAndServe(":8080", nil)
}
浏览器结果
百度
- 在不同的模板文件中定义同名的模板
模板文件
hello.html
<!-- 定义模板 -->
{{define "model"}}
<html>
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
{{template "content"}}
</body>
</html>
{{end}}
content1.html
<html>
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!-- 定义content模板 -->
{{define "content"}}
<h1>我是content1.html模板文件中的内容</h1>
{{end}}
</body>
</html>
content2.html
<html>
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!-- 定义content模板 -->
{{define "content"}}
<h1>我是content2.html模板文件中的内容</h1>
{{end}}
</body>
</html>
处理器代码
package main
import (
"html/template"
"math/rand"
"net/http"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
var t *template.Template
if rand.Intn(5) > 2 {
//解析模板文件
t = template.Must(template.ParseFiles("hello.html", "content1.html"))
} else {
//解析模板文件
t = template.Must(template.ParseFiles("hello.html", "content2.html"))
}
//执行模板
t.ExecuteTemplate(w, "model", "")
}
func main() {
http.HandleFunc("/handler", handler)
http.ListenAndServe(":8080", nil)
}
浏览器结果:
我是content1.html模板文件中的内容
块动作
go1.6引入了一个新的块动作,这个动作允许用户定义一个模板并立即使用。相当于设置了一个默认的模板
- 格式:
{{block arg}}
如果找不到模板我就要显示了
{{end}}
模板文件
define2.html
<!-- 定义模板 -->
{{define "model"}}
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 块动作,相当于一个默认的模板 -->
{{block "content" .}}
你的年龄是假的吧!
{{end}}
</body>
</html>
{{end}}
content1.html
<html>
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!-- 定义content模板 -->
{{define "content"}}
我已经是一个成年人!
{{end}}
</body>
</html>
content2.html
<html>
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!-- 定义content模板 -->
{{define "content"}}
我还未成年!
{{end}}
</body>
</html>
处理器代码
package main
import (
"html/template"
"net/http"
)
//测试testDefine2
func testDefine2(w http.ResponseWriter, r *http.Request) {
age := 17
var t *template.Template
if age < 18 {
//解析模板
t = template.Must(template.ParseFiles("define2.html"))
} else {
//解析模板
t = template.Must(template.ParseFiles("define2.html", "content1.html"))
}
//执行
t.ExecuteTemplate(w, "model", "")
}
func main() {
http.HandleFunc("/testDefine2", testDefine2)
http.ListenAndServe(":8080", nil)
}
浏览器结果:
你的年龄是假的吧!