狗狼 · 2024年1月5日

template 小抄

Go 官方库提供了两个模板库: text/templatehtml/template。这两个库类似,只不过 html/template对html格式做了特别的处理,当需要输出html格式的代码时需要使用html/template。

简单使用

package main

import (
	"os"
	"text/template"
)

type Student struct {
	Name string
}

func main() {
	tom := Student{"Tom"}
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, tom)
	if err != nil {
		panic(err)
	}
}
<h1> It is day number {{.Name}} of the month </h1>
package main

import (
    "os"
    "text/template"
)

type Student struct {
    Name string
}

func main() {
    tom := Student{"Tom"}
    // name := "Tom"
    muban := "hello, {{.Name}}"
    tmpl, err := template.New("test").Parse(muban)
    if err != nil {
        panic(err)
    }
    err = tmpl.Execute(os.Stdout, tom)
    if err != nil {
        panic(err)
    }
}

模板变量

  1. 字符

模板变量可以是boolean, string, character, integer, floating-point, imaginary 或者 complex constant。传给模板这样的数据就可以通过点号.来访问:

{ { . }}
如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
如果字段还是复杂类型,可以链式访问 { { .Struct.StructTwo.Field }}。
  1. 模板中的变量

传给模板的数据可以存在模板中的变量中,在整个模板中都能访问。 比如 { {$number := .}}, 我们使用$number作为变量,保存传入的数据,可以使用{ {$number}}来访问变量。

{ {$number := .}}
<h1> It is day number { {$number}} of the month </h1>

var tpl *template.Template
tpl = template.Must(template.ParseFiles("templateName"))
err := tpl.ExecuteTemplate(os.Stdout, "templateName", 23)
上面的例子我们把23传给模板,模板的变量$number的值是23,可以在模板中使用。

模板动作

  1. if/else 语句:使用if检查数据,如果不满足可以执行else。空值是是false, 0、nil、空字符串或者长度为0的字符串都是false。
<h1>Hello, { {if .Name}} { {.Name}} { {else}} Anonymous { {end}}!</h1>
  1. 移除空格:往模板中增加不同的值的时候可能会增加一定数量的空格。我们既可以改变我们的模板以便更好的处理它,忽略/最小化这种效果,或者我们还可以使用减号-:
<h1>Hello, { {if .Name}} { {.Name}} { {- else}} Anonymous { {- end}}!</h1>
  1. range
package main

import (
	"os"
	"text/template"
)

type Item struct {
	Name  string
	Price int
}
type ViewData struct {
	Name  string
	Items []Item
}

func main() {
	tom := ViewData{
		Name: "viewData",
		Items: []Item{
			{
				Name:  "0",
				Price: 0,
			},
			{
				Name:  "1",
				Price: 1,
			},
		},
	}
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, tom)
	if err != nil {
		panic(err)
	}
}
{{range .Items}}
  <div class="item">
    <h3 class="name">{{.Name}}</h3>
    <span class="price">${{.Price}}</span>
  </div>
{{end}}

模板函数

  1. 获取索引值: 如果传给模板的数据是map、slice、数组,那么我们就可以使用它的索引值。我们使用{ {index x number}}来访问x的第number个元素, index是关键字。比如{ {index names 2}}等价于names[2]。{ {index names 2 3 4}} 等价于 names[2][3][4](三维数组)。
package main

import (
	"os"
	"text/template"
)
type person struct {
	Name    string
	FavNums []int
}

func main() {
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, &person{"Curtis", []int{7, 11, 94}})
	if err != nil {
		panic(err)
	}
}
<body>
    <h1> {{- index .FavNums 2}}</h1>
</body>
  1. and 函数: and函数返回bool值,通过返回第一个空值或者最后一个值。and x y逻辑上相当于if x then y else x
package main

import (
	"os"
	"text/template"
)

type User struct {
	Admin bool
}
type ViewData struct {
	*User
}

func main() {
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, &ViewData{
		User: &User{Admin: false},
	})
	if err != nil {
		panic(err)
	}
}
{{if and .User .User.Admin}}
  You are an admin user!
{{else}}
  Access denied!
{{end}}
  1. or 函数: 类似and
  2. not 函数: 返回相反的值
{ { if not .Authenticated}}
  Access Denied!
{ { end }}

模板比较函数

  1. 比较: 数据的类型只能是基本类型和命名的基本类型,比如type Temp float3,格式是{ { function arg1 arg2 }}。
  • eq: arg1 == arg2
  • ne: arg1 != arg2
  • lt: arg1 < arg2
  • le: arg1 <= arg2
  • gt: arg1 > arg2
  • ge: arg1 >= arg2

eq函数比较特殊,可以拿多个参数和第一个参数进行比较。{ { eq arg1 arg2 arg3 arg4}}逻辑是arg1==arg2 || arg1==arg3 || arg1==arg4。

嵌套模板和布局

  1. 嵌套模板: 嵌套模板可以用做跨模板的公共部分代码,比如 header或者 footer。使用嵌套模板我们就可以避免一点小小的改动就需要修改每个模板。嵌套模板定义如下:
{ {define "footer"}}
<footer> 
	<p>Here is the footer</p>
</footer>
{ {end}}
{ {template "footer"}}
  1. 模板之间传递变量
// Define a nested template called header
{{define "header"}}
	<h1>{ {.}}</h1>
{{end}}
// Call template and pass a name parameter
{{range .Items}}
  <div class="item">
    {{template "header" .Name}}
    <span class="price">${ {.Price}}</span>
  </div>
{{end}}
  1. 创建布局

Glob模式通过通配符匹配一组文件名。template.ParseGlob(pattern string)会匹配所有符合模式的模板。template.ParseFiles(files...)也可以用来解析一组文件。
模板默认情况下会使用配置的参数文件名的base name作为模板名。这意味着views/layouts/hello.gohtml的文件名是hello.gohtml,如果模板中有{ {define “templateName”}}的话,那么templateName会用作这个模板的名字。

模板可以通过t.ExecuteTemplate(w, "templateName", nil)来执行, t是一个类型为Template的对象,w的类型是io.Writer,比如http.ResponseWriter,然后是要执行的模板的名称,以及要传入的数据:

var LayoutDir string = "views/layouts"  
var bootstrap *template.Template
func main() {
	var err error
	bootstrap, err = template.ParseGlob(LayoutDir + "/*.gohtml")
	if err != nil {
		panic(err)
	}
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
	bootstrap.ExecuteTemplate(w, "bootstrap", nil)
}

所有的.gohtml文件都被解析,然后当访问/的时候,bootstrap会被执行。

views/layouts/bootstrap.gohtml定义如下:

{{define "bootstrap"}}
<!DOCTYPE html>  
<html lang="en">  
  <head>
    <title>Go Templates</title>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" 
	rel="stylesheet">
  </head>
  <body>
    <div class="container-fluid">
      <h1>Filler header</h1>
	  <p>Filler paragraph</p>
    </div>
    <!-- jquery & Bootstrap JS -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"  
    </script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js">
    </script>
  </body>
</html>  
{{end}}

模板调用函数

  1. 函数变量(调用结构体的方法)
package main

import (
	"os"
	"text/template"
)

type User struct {
	ID    int
	Email string
}

func (u User) HasPermission(feature string) bool {
	if feature == "feature-a" {
		return true
	} else {
		return false
	}
}

func main() {
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, User{})
	if err != nil {
		panic(err)
	}
}
type User struct {  
  ID    int
  Email string
}
func (u User) HasPermission(feature string) bool {  
  if feature == "feature-a" {
    return true
  } else {
    return false
  }
}
{{ if .HasPermission "feature-a" }}
  <div class="feature">
    <h3>Feature A</h3>
    <p>Some other stuff here...</p>
  </div>
{{ else }}
  <div class="feature disabled">
    <h3>Feature A</h3>
    <p>To enable Feature A please upgrade your plan</p>
  </div>
{{ end }}
  1. 函数变量(内部函数): 使用 call 调用函数
package main

import (
	"os"
	"text/template"
)

type User struct {
	ID            int
	Email         string
	HasPermission func(string) bool
}

func main() {
	tmpl, err := template.New("hello.tpl").ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, User{
		HasPermission: func(s string) bool {
			if s == "a" {
				return true
			}
			return false
		},
	})
	if err != nil {
		panic(err)
	}
}
{{ if call .HasPermission "feature-c" }}
  <div class="feature">
    <h3>Feature A</h3>
    <p>Some other stuff here...</p>
  </div>
{{ else }}
  <div class="feature disabled">
    <h3>Feature A</h3>
    <p>To enable Feature A please upgrade your plan</p>
  </div>
{{ end }}
  1. 自定义函数

另外一种方式是使用template.FuncMap创建自定义的函数,它创建一个全局的函数,可以在整个应用中使用。FuncMap通过map[string]interface{}将函数名映射到函数上。注意映射的函数必须只有一个返回值,或者有两个返回值但是第二个是error类型。

package main

import (
	"os"
	"text/template"
)

type User struct {
	ID            int
	Email         string
	HasPermission func(string) bool
}

func main() {
	tmpl, err := template.New("hello.tpl").Funcs(
		template.FuncMap{
			"hasPermission": func(s string) bool {
				if s == "a" {
					return true
				}
				return false
			},
		}).ParseFiles("hello.tpl")
	if err != nil {
		panic(err)
	}
	err = tmpl.Execute(os.Stdout, nil)
	if err != nil {
		panic(err)
	}
}
{{ if hasPermission "feature-a" }}
  <div class="feature">
    <h3>Feature A</h3>
    <p>Some other stuff here...</p>
  </div>
{{ else }}
  <div class="feature disabled">
    <h3>Feature A</h3>
    <p>To enable Feature A please upgrade your plan</p>
  </div>
{{ end }}
  1. 第三方自定义函数

除了官方的预定义的函数外,一些第三方也定义了一些函数,你可以使用这些库,避免重复造轮子。

比如sprig库,定义了很多的函数: