模板庫介紹
如果你有過Web編程的經驗,那麼或多或少都聽説過或者使用過模板。簡而言之,模板是可用於創建動態內容的文本文件。例如,你有一個網站導航欄的模板,其中動態內容的一部分可能是根據當前用户是否登錄顯示登錄還是退出按鈕。
Go提供了兩個模板庫text/template和html/template。這兩個模板庫的使用方式是相同的,但是html/template包在渲染頁面模板時會在後台進行一些編碼以幫助防止造成代碼注入(XSS 攻擊)。
因為兩個模板庫都使用相同的接口,因此本文中介紹的所有內容均可用於這兩個程序包,但是大多數時候我們都會使用html/template程序包來生成HTML代碼段。
Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供大家參考。公眾號中回覆gohttp07獲取本文源代碼
模板文件的後綴名
模板文件可以使用.html或任何其他擴展名。但是通常我們將使用.gohtml擴展名來命名模板文件,因為編輯器通常使用它來表示你想要高亮Go HTML模板語法。 Atom和Sublime Text等編輯器都具有Go插件,來默認識別此擴展名。
模板語法
我們先來創建一個簡單的模板文件test.gohtml:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web</title>
</head>
<body>
{{ . }}
</body>
</html>
{{ 和 }} 中間的半角句號 . 它代表模板對象執行Execute(w, data)傳入模板的數據,它是頂級作用域範圍內的,根據傳入的數據不同渲染不同的內容。. 可以代表Go語言中的任何類型,如結構體、Map等。
在寫模板的時候,會經常用到.。比如{{.}}、{{len .}}、{{.Name}}、{{$x.Name}}
{{ 和 }} 包裹的內容統稱為 action,分為兩種類型:
- 數據求值(data evaluations)
- 控制結構(control structures)
action求值的結果會直接複製到模板中,控制結構和我們寫Go程序差不多,也是條件語句、循環語句、變量、函數調用等等...模板中的 action 並不多,我們一個一個看。
註釋
{{/* comment */}}
裁剪空字符
注意裁剪的是替換內容前面或者後面的空字符,你可以理解成模板中{{前面或}}後面的空字符(包括換行符、製表符、空格等)。
// 裁剪 content 前後的空字符
{{- content -}}
// 裁剪 content 前面的空字符
{{- content }}
// 裁剪 content 後面的空字符
{{ content -}}
文本輸出
{{ pipeline }}
pipeline代表的數據會產生與調用 fmt.Print 函數類似的輸出,例如整數類型的 3 會轉換成字符串 "3" 輸出。
條件語句
{{ if pipeline }} T1 {{ end }}
{{ if pipeline }} T1 {{ else }} T0 {{ end }}
{{ if pipeline }} T1 {{ else if pipeline }} T0 {{ end }}
// 上面的語法其實是下面的簡寫
{{ if pipeline }} T1 {{ else }}{{ if pipeline }} T0 { {end }}{{ end }}
{{ if pipeline }} T1 {{ else if pipeline }} T2 {{ else }} T0 {{ end }}
如果 pipeline 的值為空,不會輸出 T1,除此之外 T1 都會被輸出。
空值有false、0、 nil空字符串 ""(長度為 0 的字符串)。
循環語句
{{ range pipeline }} T1 {{ end }}
// 這個 else 比較有意思,如果 pipeline 的長度為 0 則輸出 else 中的內容
{{ range pipeline }} T1 {{ else }} T0 {{ end }}
// 獲取容器的下標
{{ range $index, $value := pipeline }} T1 {{ end }}
循環語句中的pipeline 的值必須是數組、切片、字典和通道中的一種,即可迭代類型的值,根據值的長度輸出多個 T1。
define
{{ define "name" }} T {{ end }}
定義命名為 name 的模板。
template
{{ template "name" }}
{{ template "name" pipeline }}
第一種是直接執行名為name的模板,模板的全局數據對象.設置為nil。第二種是點.設置為pipeline的值,並執行名為name的模板。
block
{{ block "name" pipeline }} T1 {{ end }}
block 的語義是如果有命名為 name 的模板,就引用過來執行,如果沒有命名為 name 的模板,就是執行自己定義的內容。換句話説,block可以認為是設置一個默認模板。
with
{{ with pipeline }} T1 {{ end }}
// 如果 pipeline 是空值則輸出 T0
{{ with pipeline }} T1 {{ else }} T0 {{ end }}
{{ with arg }}
. // 此時 . 就是 arg
{{ end }}
with 創建一個新的上下文環境,在此環境中的 . 與外面的 . 無關。
對於第一種格式,當pipeline不為0值的時候,點.設置為pipeline運算的值,否則跳過。對於第二種格式,當pipeline為0值時,執行else語句塊,否則.設置為pipeline運算的值,並執行T1。
例如:
{{with .Person}}{{ .Name}}{{end}}
在這個 with 塊中.Name實際上引用的是全局數據對象的.Person.Name。
實踐練習:課程花名冊頁面
瞭解完模板語法後,接下來讓我們再http_demo項目中結合BootStrap創建一個簡單的模板,來展示服務器如何把數據傳遞給模板、渲染HTML頁面,把頁面響應返回給客户端。
我們創建一個用來展示大學物理課程的花名冊(授課老師和上課學生)
創建頁面模板
首先在我們的項目添加一個views目錄用於存放模板文件,在創建三個模板文件分別是:
layout.gohtml 用於存放頁面的整體佈局。
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<title>Bootstrap Template Page for Go Web Programming</title>
<!-- Bootstrap core CSS -->
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
{{ template "nav" .}}
<div class="container">
{{template "content" .}}
</div>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
</html>
nav.gohtml是網頁頭部區域的頁面模板。
{{define "nav"}}
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Person general infor</a>
</div>
</div>
</nav>
<div class="jumbotron">
<div class="container">
<h1>Hello, Professor {{.Teacher.Name}}</h1>
<ul>
<li>Name : {{.Teacher.Name}}<p>
<li>Subject : {{.Teacher.Subject}}
</ul>
<p><a class="btn btn-primary btn-lg" href="#" role="button">More »</a></p>
</div>
</div>
{{end}}
content.gohtml是網頁主體內容部分的頁面模板。
{{define "content"}}
{{range .Students}}
<div class="row">
<div class="col-md-4">
<h2>Name</h2>
<p>Name has the value of : {{.Name}} </p>
<p><a class="btn btn-default" href="#" role="button">More »</a></p>
</div>
<div class="col-md-4">
<h2>Id</h2>
<p>Id has the value of : {{.Id}} </p>
<p><a class="btn btn-default" href="#" role="button">More »</a></p>
</div>
<div class="col-md-4">
<h2>Country</h2>
<p>Country has the value of : {{.Country}} </p>
<p><a class="btn btn-default" href="#" role="button">More »</a></p>
</div>
</div>
{{end}}
{{end}}
在layout.gohtml中我們引用了另外的兩個模板:
{{ template "nav" .}}
{{template "content" .}}
這樣不同的頁面變化的部分就只是content部分,針對不同的頁面我們只需要定義多個content模板,每次根據不同請求使用不同的content模板就行了。當然這裏的例子有點簡陋,大家理解意思就行了。
注意模板名稱後面的.,我們把layout.gohtml的全局數據對象傳給了另外兩個模板這樣,在子模板裏也能訪問傳給模板的數據了。如果頁面模板中使用的數據字段和循環語句有點疑惑可以先不用管,繼續往下看,等看過傳給頁面模板的數據後自然就理解了。
創建響應頁面請求的Handler
接下來創建一個伺服頁面請求的Handler:
package handler
import (
"fmt"
"html/template"
"net/http"
)
type Teacher struct {
Name string
Subject string
}
type Student struct {
Id int
Name string
Country string
}
type Rooster struct {
Teacher Teacher
Students []Student
}
func ShowIndexView(response http.ResponseWriter, request *http.Request) {
teacher := Teacher{
Name: "Alex",
Subject: "Physics",
}
students := []Student{
{Id: 1001, Name: "Peter", Country: "China"},
{Id: 1002, Name: "Jeniffer", Country: "Sweden"},
}
rooster := Rooster{
Teacher: teacher,
Students: students,
}
tmpl, err := template.ParseFiles("./views/layout.gohtml",
"./views/nav.gohtml",
"./views/content.gohtml")
if err != nil {
fmt.Println("Error " + err.Error())
}
tmpl.Execute(response, rooster)
}
使用template.ParseFiles加載這個頁面要使用的全部三個模板(如果加載少了,訪問頁面時會發生panic),然後使用模板對象的 Execute方法把我們存儲了花名冊信息的數據對象傳給模板: tmpl.Execute(response, rooster) 渲染頁面並寫到響應裏去(http.ResponseWriter對象)。
註冊頁面路由
處理程序寫完後,為其註冊路由,在我們項目的路由模塊添加如下路由:
package router
import (
"example.com/http_demo/middleware"
"github.com/gorilla/mux"
"example.com/http_demo/handler"
)
func RegisterRoutes(r *mux.Router) {
r.Use(middleware.Logging())
...
viewRouter := r.PathPrefix("/view").Subrouter()
viewRouter.HandleFunc("/index", handler.ShowIndexView)
}
訪問頁面
現在所有步驟都完成了,重啓我們的服務器後就可以訪問到新寫的頁面了。
如果是在本地電腦裏,用Ctrl+C結束服務器進程後再次執行go run main.go。如果是使用我們之前文章裏的Docker開發環境的話(公眾號回覆:go-docker 獲取Docker環境的安裝指南)需要在docker-compose.yml所在的目錄裏用docker-compose restart重啓服務。
打開瀏覽器輸入http://localhost:8000/view/index就能訪問到我們剛才寫的頁面了。
總結
今天的文章講解了Go模板最常使用的幾個功能的使用方法,使用html/template模板庫結合BootStrap做頁面模板,還是比較簡單的BootStrap幫我們解決了很多前端的樣式問題。模板庫還有很多更高級的用法,比如在模板中調用函數、定義變量等功能,可以看下文末給出的參考鏈接瞭解這些內容。在前後端分離架構流行的今天我覺得作為用Go開發的後端工程師瞭解文章中列出的這些功能就夠了。
今天的例子中是通過 CDN 引用的BootStrap靜態資源,到目前我們的服務器還無法伺服靜態資源,這個我們下篇文章再講。公眾號回覆gohttp07即可獲取今天文章中示例代碼的下載鏈接。如果覺得我的文章有收穫,請幫忙分享給更多人。
參考
Go 語言標準庫 text/template 包深入淺出
An Introduction to Templates in Go
前文回顧
深入學習用Go編寫HTTP服務器
設置HTTP服務器的路由
Go Web編程--應用ORM
Go Web編程--深入學習解析HTTP請求