Go Web 編程--超詳細的模板庫應用指南

模板庫介紹

若是你有過Web編程的經驗,那麼或多或少都據說過或者使用過模板。簡而言之,模板是可用於建立動態內容的文本文件。例如,你有一個網站導航欄的模板,其中動態內容的一部分多是根據當前用戶是否登陸顯示登陸仍是退出按鈕。css

Go提供了兩個模板庫text/templatehtml/template。這兩個模板庫的使用方式是相同的,可是html/template包在渲染頁面模板時會在後臺進行一些編碼以幫助防止形成代碼注入(XSS 攻擊)。html

由於兩個模板庫都使用相同的接口,所以本文中介紹的全部內容都可用於這兩個程序包,可是大多數時候咱們都會使用html/template程序包來生成HTML代碼段。前端

Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供你們參考。公衆號中回覆gohttp07獲取本文源代碼git

模板文件的後綴名

模板文件可使用.html或任何其餘擴展名。可是一般咱們將使用.gohtml擴展名來命名模板文件,由於編輯器一般使用它來表示你想要高亮Go HTML模板語法。 AtomSublime Text等編輯器都具備Go插件,來默認識別此擴展名。github

模板語法

咱們先來建立一個簡單的模板文件test.gohtml:docker

<!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}}bootstrap

{{ 和 }} 包裹的內容統稱爲 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 都會被輸出。

空值有false0nil空字符串 ""(長度爲 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 &raquo;</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 &raquo;</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 &raquo;</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 &raquo;</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就能訪問到咱們剛纔寫的頁面了。

l

總結

今天的文章講解了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請求

相關文章
相關標籤/搜索