GoWeb 模板引擎

go提供了text/template库和 html/template库这两个模板引擎,模板引擎通过将数据和模板组合在一起生成最终的HTML,而处理器负责调用模板引擎并将引擎生成的HTML返回给客户端

go的模板都是文本文档(其中web应用的模板通常都是HTML),它们都嵌入了一些称为动作的指令。从模板引擎的角度来说,模板就是嵌入了动作的文本(这些文本通常包含在模板文件里面),而模板引擎则通过分析并执行这些文本来生成出另外一些文本

HelloWorld

使用go的web模板引擎需要以下两个步骤:

  1. 对文本格式的模板源进行语法分析,创建一个经过语法分析的模板结构,其中模板源既可以是一个字符串,也可以是模板文件中包含的内容
  2. 执行经过语法分析的模板,将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

解析模板

  1. 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"))
  1. ParseGlob函数

func ParseGlob

func ParseGlob(pattern string) (*Template, error)

ParseGlob创建一个模板并解析匹配pattern的文件(参见glob规则)里的模板定义。返回的模板的名字是第一个匹配的文件的文件名(不含扩展名),内容为解析后的第一个文件的内容。至少要存在一个匹配的文件。如果发生错误,会停止解析并返回nil。ParseGlob等价于使用匹配pattern的文件的列表为参数调用ParseFiles

通过该函数可以通过指定一个规则一次性传入多个模板文件,如:

t, _ := template.ParseGlob("*.html")

执行模板

  1. 通过

func (*Template) Execute

func (t *Template) Execute(wr io.Writer, data interface{}) error

Execute方法将解析好的模板应用到data上,并将输出写入wr。如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。模板可以安全的并发执行

  • 如果只有一个模板文件,调用这个方法总是可行的;但是如果有多个模板文件,调用这个方法只能得到第一个模板
  1. 通过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}}

说明:

  1. range后面的点 代表被遍历的元素
  2. 要显示的内容里面的点 代表遍历到的元素

模板文件

<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}}
  1. 在一个模板文件中定义多个模板

模板文件
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)
}

浏览器结果
百度

  1. 在不同的模板文件中定义同名的模板

模板文件
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)
}

浏览器结果:

你的年龄是假的吧!
发布了116 篇原创文章 · 获赞 27 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wuxingge/article/details/105267929