模板引擎
Go为我们提供了text/template库和html/template库这两个模板引擎,模板引擎通过将数据和模板组合在一起生成最终的HTML,而处理器负责调用模板引擎并将引擎生成的HTMl返回给客户端。
Go的模板都是文本文档(其中Web应用的模板通常都是HTML),它们都嵌入了一些称为动作的指令。从模板引擎的角度来说,模板就是嵌入了动作的文本(这些文本通常包含在模板文件里面),而模板引擎则通过分析并执行这些文本来生成出另外一些文本。
快速入门
使用Go的Web模板引擎需要以下两个步骤:
(1) 对文本格式的模板源进行语法分析,创建一个经过语法分析的模板结构,其中模板源既可以是一个字符串,也可以是模板文件中包含的内容。
(2) 执行经过语法分析的模板,将ResponseWriter和模板所需的动态数据传递给模板引擎,被调用的模板引擎会把经过语法分析的模板和传入的数据结合起来,生成出最终的HTML,并将这些HTML传递给ResponseWriter。
创建模板文件webapp/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
//嵌入动作
{{.}}
</body>
</html>
在处理器中触发模板引擎
import (
"html/template"
"log"
"net/http"
"os"
)
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t, _ := template.ParseFiles("hello.html")
//执行模板
t.Execute(w,"Hello world")
}
func init() {
file := "./" +"message"+ ".txt"
logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
if err != nil {
panic(err)
}
log.SetOutput(logFile) // 将文件设置为log输出的文件
log.SetPrefix("[qSkipTool]")
log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC)
return
}
func main() {
http.HandleFunc("/hello", handler)
err := http.ListenAndServe(":8080",nil)
if err != nil {
log.Println("ListenAndServer: ", err)
}
}
浏览器中的结果
解析模板
- ParseFiles函数
当我们调用ParseFiles函数解析模板文件时,Go会创建一个新的模板,并将给定的模板文件的名字作为新模板的名字,如果该函数中传入了多个文件名,那么也只会返回一个模板,而且以第一个文件的文件名作为模板的名字,至于其他文件对应的模板则会被放到一个map中。让我们再来看一下HelloWorld中的代码:
t, _ := template.ParseFiles("hello.html")
以上代码相当于调用New函数创建一个新模板,然后再调用template的ParseFiles方法:
t := template.New("hello.html")
t, _ = t.ParseFiles("hello.html")
以上方法在解析模板时都没有对错误进行处理,Go提供了一个Must函数专门用来处理这个错误。Must函数可以包裹起一个函数,被包裹的函数会返回一个指向模板的指针和一个错误,如果错误不是nil,那么Must函数将产生一个panic。
Must函数代码
//t, _ := template.ParseFiles("hello.html")
t := template.Must(template.ParseFiles("hello.html"))
- ParseGlob函数
执行模板
-
通过Execute方法
如果只有一个模板文件,调用这个方法总是可行的;但是如果有多个模板文件,调用这个方法只能得到第一个模板 -
通过ExecuteTemplate方法
main.go 文件
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
//t, _ := template.ParseFiles("hello.html","hello2.html")
//t := template.Must(template.ParseFiles("hello.html"))
t, _ := template.ParseGlob("*.html")
//执行模板
//t.Execute(w,"Hello world")
t.ExecuteTemplate(w,"hello2.html","要在hello2.html中显示")
}
hello2.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件2</title>
</head>
<body>
//嵌入动作
{{.}}
</body>
</html>
变量t就是一个包含了两个模板的模板集合,第一个模板的名字是hello.html,第二个模板的名字是hello2.html,如果直接调用Execute方法,则只有模板hello.html会被执行,如何想要执行模板hello2.html,则需要调用ExecuteTemplate方法
动作
Go模板的动作就是一些嵌入到模板里面的命令,这些命令在模板中需要放到两个大括号里{{ 动作 }},之前我们已经用过一个很重要的动作:点(.),它代表了传递给模板的数据。下面我们再介绍几个常用的动作,如果还想了解其他类型的动作,可以参考text/template库的文档。
条件动作
格式一:
{{ if arg}}
要显示的内容
{{ end }}
格式二:
{{ if arg}}
要显示的内容
{{else}}
当if条件不满足时要显示的内容
{{ end }}
其中的arg是传递给条件动作的参数,该值可以是一个字符串常量、一个变量、一个返回单个值的函数获取方法等。
代码示例:
webapp/html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!--嵌入动作-->
{{if .}}
你已经成年了
{{else}}
你还未成年
{{end}}
</body>
</html>
webapp/main.go
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//声明一个变量
age := 16
//执行模板
t.Execute(w, age > 18)
}
浏览器
迭代动作
迭代动作可以对数组、切片、映射或者通道进行迭代。
格式一:
{{range . }}
遍历到的元素是 {{ . }}
{{ end }}
格式二:
{{range . }}
遍历到的元素是 {{ . }}
{{ else }}
没有任何元素
{{ end }}
range后面的点代表被遍历的元素;要显示的内容里面的点代表遍历到的元素
代码示例
webapp/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!--嵌入动作-->
{{range .}}
<!-- href 属性用于指定超链接目标的 URL-->
<a href="#">{{.}}</a>
{{else}}
没有遍历到任何内容
{{end}}
</body>
</html>
webapp/main.go
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//声明一个字符串切片
stars := []string{"马蓉","李小璐","白百何"}
//执行模板
t.Execute(w, stars)
}
浏览器
如果迭代之后是一个个的结构体,获取结构体中的字段值使用 *.字段名* 方式获取
{{range . }}
获取结构体的Name字段名 {{ .Name }}
{{ end }}
迭代Map时可以设置变量,变量以$开头:
{{ range $k , $v := . }}
键是 {{ $k }} , 值是 {{ $v }}
{{ end }}
迭代管道
{{ c1 | c2 | c3 }}
c1、c2和c3可以是参数或者函数。管道允许用户将一个参数的输出传递给下一个参数,各个参数之间使用 | 分割。
设置动作
设置动作允许在指定的范围内对点(.)设置值。
格式一:
{{ with arg }}
为传过来的数据设置的新值是{{ . }}
{{ end }}
格式二:
{{ with arg }}
为传过来的数据设置的新值是{{ . }}
{{ else }}
传过来的数据仍然是{{ . }}
{{ end }}
代码示例:
webapp/hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!--嵌入动作-->
<div>得到的数据是: {{.}}</div>
{{with "太子"}}
<div>替换之后的数据是: {{.}}</div>
{{end}}
<hr/>
{{with ""}}
<div> 看一下现在的数据是: {{.}}</div>
{{else}}
<div> 数据没有被替换,还是: {{.}} </div>
{{end}}
<hr/>
</body>
</html>
webapp/main.go
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//执行模板
t.Execute(w, "狸猫")
}
浏览器
包含动作
包含动作允许用户在一个模板里面包含另一个模板,从而构建出嵌套的模板。
格式一:
{{ template “name” }}
name为被包含的模板的名字
格式二:
{{ template “name” arg }}
arg是用户想要传递给被嵌套模板的数据
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板文件</title>
</head>
<body>
<!--嵌入动作-->
<div> 从后台得到的数据是: {{.}} </div>
<!--包含hello2.html模板-->
{{template "hello2.html"}}
<div> hello.html文件内容结束</div>
<hr/>
<div>将hello.html模板文件中的数据传递给hello2.html模板文件</div>
{{template "hello2.html" .}}
</body>
</html>
webapp/hello2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello2模板文件</title>
</head>
<body>
<!--嵌入动作-->
<div>hello2.html模板文件中的数据是: {{.}}</div>
</body>
</html>
webapp/main.go
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseGlob("*.html"))
//执行模板
t.Execute(w, "测试包含")
}
注意:在解析模板文件时,当前文件以及被包含的文件都要解析。
浏览器
定义动作
当我们访问一些网站时,经常会看到好多网页中有相同的部分:比如导航栏、版权信息、联系方式等。这些相同的布局我们可以通过定义动作在模板文件中定义模板来实现。定义模板的格式是:以{{ define “layout” }}开头,以{{ end }}结尾。
- 在一个模板文件(hello.html)中定义一个模板
<!-- 定义模板 -->
{{ define "model"}}
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
{{ template "content"}}
</body>
</html>
{{ end }}
- 在一个模板文件(hello.html)中定义多个模板
<!-- 定义模板 -->
{{ define "model"}}
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
{{ template "content"}}
</body>
</html>
{{ end }}
{{ define "content"}}
<a href="#">点我有惊喜</a>
{{ end }}
- webapp/main.go
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
//解析模板文件
t := template.Must(template.ParseFiles("hello.html"))
//执行模板
t.ExecuteTemplate(w,"model","")
}
注意:需要调用ExecuteTemplate方法并指定模板的名字
浏览器
4) 在不同的模板文件中定义同名的模板
hello.html
<!-- 定义模板 -->
{{ define "model"}}
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
{{ template "content"}}
</body>
</html>
{{ end }}
content1.html
<html>
<head>
<title>content模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
<!-- 定义content模板 -->
{{ define “content” }}
<h1>我是content1.html模板文件中的内容</h1>
{{ end }}
</body>
</html>
content2.html
<html>
<head>
<title>content模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
<!-- 定义content模板 -->
{{ define “content” }}
<h1>我是content2.html模板文件中的内容</h1>
{{ end }}
</body>
</html>
webapp/main.go
//创建处理器函数
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","")
浏览器
块动作
Go 1.6引入了一个新的块动作,这个动作允许用户定义一个模板并立即使用。相当于设置了一个默认的模板
格式:
{{ block arg }}
如果找不到模板我就要显示了
{{ end }}
webapp/hello.html
<!--定义模板-->
{{define "model"}}
<html>
<head>
<title>模板文件</title>
<meta charset="utf-8"/>
</head>
<body>
{{block "content" .}}
如果找不到就显示我
{{end}}
</body>
</html>
{{end}}
webapp/main.go
package main
import (
"html/template"
"log"
"math/rand"
"net/http"
"os"
"time"
)
type Inventory struct {
Material string
Count uint
}
//创建处理器函数
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"))
}
//执行模板
t.ExecuteTemplate(w,"model","")
}
func init() {
file := "./" +"message"+ ".txt"
logFile, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0766)
if err != nil {
panic(err)
}
log.SetOutput(logFile) // 将文件设置为log输出的文件
log.SetPrefix("[qSkipTool]")
log.SetFlags(log.LstdFlags | log.Lshortfile | log.LUTC)
return
}
func main() {
http.HandleFunc("/hello", handler)
err := http.ListenAndServe(":8080",nil)
if err != nil {
log.Println("ListenAndServer: ", err)
}
}
浏览器
注:参考 尚硅谷--> 韩顺平go系列
李文周 --> https://www.liwenzhou.com/posts/Go/go_template/