模板引擎
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/