四、Gin模板
模板在web开发中⼴泛使⽤,它能够有效的将业务逻辑和页⾯逻辑分开,使代码可读性增强、并且更加容易理解和维护。 模板简单来说就是⼀个其中包涵占位变量表⽰动态的部分的⽂件,模板⽂件在经过动态赋值后,返回给⽤户。
4.1 、变量渲染
视图部分:
package main
import "github.com/gin-gonic/gin"
type Student struct {
Name string
Age int
}
func main() {
// 基于引擎对象,可以理解为路由对象,
r := gin.Default()
// 加载模版文件
r.LoadHTMLGlob("templates/*")
r.GET("/index", func(context *gin.Context) {
context.HTML(200, "index.html", gin.H{
"user": "June",
"state": "在线",
"booksSlice": []string{"西游记", "三国演义", "聊斋", "水浒传"},
"stuMap": map[string]interface{}{
"name": "summer",
"age": 23,
"hobby": []string{"攀岩", "爬山", "跑步"},
},
"stuStruct": Student{Name: "June", Age: 27},
})
})
// 启动:默认本机的8080端口
r.Run()
}
模版html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3>变量渲染</h3>
{{ . }}
<p>{{.booksSlice}}</p>
<p>{{index .booksSlice 0 }}</p>
<p>{{index .booksSlice 1}}</p>
<p>{{index .booksSlice 2}}</p>
<p>{{.stuMap}}</p>
<p>{{.stuMap.name}}</p>
<p>{{.stuStruct}}</p>
<p>{{.stuStruct.Name}}</p>
</body>
</html>
切片的深度查询依靠内置函数index,map对象和结构体对象的深度查询通过句点符实现!
4.2 、控制结构
(1)分支结构
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
表达式为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。
<h3>分支结构</h3>
{{if gt .stuStruct.Age 18 }}
<p>{{index .booksSlice 0 }}</p>
<p>{{index .booksSlice 1}}</p>
<p>{{index .booksSlice 2}}</p>
<p>{{index .booksSlice 3}}</p>
{{else}}
<p>校园区</p>
{{end}}
(2)循环结构
表达式为false的情况是各种数据对象的0值:数值0,指针或接口是nil,数组、slice、map或string则是len为0。
{{range $value := .}} {{end}}
{{range $key,$value := .}} {{end}}
如果range中只赋值给一个变量,则这个变量是当前正在迭代元素的值。如果赋值给两个变量,则第一个变量是索引值,第二个变量是当前正在迭代元素的值。
$
开头
<h3>循环结构</h3>
<p>四大名著</p>
{{range $index,$value := .booksSlice}}
<p>{{$index}} : {{$value}} </p>
{{end}}
<p>stuMap学生的所有爱好</p>
{{range $index,$value := .stuMap.hobby}}
<p>{{$index}} : {{$value}} </p>
{{end}}
(3)变量赋值
可以在template中定义变量:
// 未定义过的变量
{{$var := pipeline}}
. 是有作用域的
// 展示年龄大于rain的所有学生
// "students": []Student{{Name: "yuan", Age: 22}, {Name: "alvin", Age: 16}, {Name: "eric", Age: 23}},
// 错误写法
{{range $index,$student := .students}}
{{if gt $student.Age .stuMap.age }} // 此时的.已经不是全局的对象,而是当前遍历的$student对象
<p>{{$student.Name}},{{$student.Age}}</p>
{{end}}
{{end}}
// 正确写法
{{$rainAge := .stuMap.age}}
{{range $index,$student := .students}}
{{if gt $student.Age $rainAge }}
<p>{{$student.Name}},{{$student.Age}}</p>
{{end}}
{{end}}
(4)注释
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。
{{/* a comment */}}
{/ 一定要紧贴
(1)默认模板函数
语法格式:
functionName [Argument...]
Argument参数是可选的,如果有多个参数,参数直接用空格发分隔。
函数名 | 函数调用格式 | 对应关系运算 | 说明 |
---|---|---|---|
eq | eq arg1 arg2 | arg1 == arg2 | arg1等于arg2则返回true |
ne | ne arg1 arg2 | arg1 != arg2 | arg1不等于arg2则返回true |
lt | lt arg1 arg2 | arg1 < arg2 | arg1小于arg2则返回true |
le | le arg1 arg2 | arg1 <= arg2 | arg1小于等于arg2则返回true |
gt | gt arg1 arg2 | arg1 > arg2 | arg1大于arg2则返回true |
ge | ge arg1 arg2 | arg1 >= arg2 | arg1大于等于arg2则返回true |
and | and 表达式1 表达式2 | 表达式1 && 表达式2 | 表达式1和表达式2都为真的时候返回true |
or | or 表达式1 表达式2 | 表达式1 || 表达式2 | 表达式1和表达式2其中一个为真的时候返回true |
not | not 表达式 | !表达式 | 表达式为false则返回true, 反之返回false |
index | index arg 索引/键 | index x 2 即x[2] | 每个被索引的主体必须是数组、切片或者字典 |
len | len arg | len x 即x的元素个数 | 用于计算数组大小 |
urlquery | urlquery arg | urlquery url | 用于url编码 |
(2)自定义模板函数
视图部分:
func main() {
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"add": func(x, y int) int {
return x + y
},
})
// 返回一个html页面
router.LoadHTMLGlob("templates/*")
router.GET("/index", index)
router.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
模版部分:
{{add 2 3}}
block template
功能,可以通过"https://github.com/gin-contrib/multitemplate"
import (
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
)
(1) 嵌套:define和template
在实际项目中,我们不可能只有一个模板,一般来说都有很多个模板,而且这些模板也会共享一些公共的模板,这些公共的模板我们都可以定义成子模板,在需要的时候调用子模板,就可以将子模板的内容嵌入当前模板中。
提示:在项目中使用子模板,可以让项目模板具有模块化的能力,提高模块复用能力和可维护性。
define可以直接在待解析内容中定义一个模板,定义了模板之后,可以使用template这个action来执行模板。template有两种格式:
{{template "name"}}
{{template "name" pipeline}}
第一种是执行名为name和template,点击设置为nil,第二种是点"."设置为pipline的值,并执行名为name的template。可以将template看做是函数:
template("name)
template("name",pipeline)
示例:在t1页面中和t2页面中嵌入一个广告页面
package main
import (
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
)
func createMyRender() multitemplate.Renderer {
r := multitemplate.NewRenderer()
r.AddFromFiles("t1.html", "templates/t1.html", "templates/adv.html")
r.AddFromFiles("t2.html", "templates/t2.html", "templates/adv.html")
return r
}
func main() {
// 基于获取引擎对象,可以理解为路由对象
r := gin.Default()
// 加载模板文件
//r.LoadHTMLGlob("templates/*")
r.HTMLRender = createMyRender()
r.GET("/test01", func(context *gin.Context) {
context.HTML(200, "t1.html", nil)
})
r.GET("/test02", func(context *gin.Context) {
context.HTML(200, "t2.html", nil)
})
// 启动:默认本机的8080端口
r.Run()
}
t1.html,t2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9">
<title>Go Web Programming</title>
</head>
<body>
<div>This is t1.html</div>
{{ template "adv.html" }}
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9">
<title>Go Web Programming</title>
</head>
<body>
<div>This is t2.html</div>
{{ template "adv.html" }}
</body>
</html>
adv.html
<div style="background-color: rebeccapurple;line-height: 200px;color: white;text-align: center">
这是一条广告信息<br/>
</div>
(2)继承:block块
根据官方文档的解释:block等价于define定义一个模板,并在"有需要"的地方执行这个模板,执行时将"."设置为pipeline的值。
但应该注意,block的第一个动作是执行名为name的模板,如果不存在,则在此处自动定义这个模板,并执行这个临时定义的模板。换句话说,block可以认为是设置一个默认模板。
例如:
{{block "T1" .}} one {{end}}
{{template "T1" .}}
,也就是说先找到T1模板,如果T1存在,则执行找到的T1,如果没找到T1,则临时定义一个{{define "T1"}} one {{end}}
{{ define "content" }}
{{ end }}
如果使用block,那么可以设置默认的content模板。例如将原本定义在blue.html中的content设置为默认模板。
...
{{block "title" .}}
<title>Theme Template for Bootstrap</title>
{{end}}
...
<div class="content">
{{block "content" .}}
{{end}}
</div>
{{template "base.html" .}}
{{define "title"}}
<title>学生管理</title>
{{end}}
{{define "content"}}
<h3>学生管理</h3>
{{end}}
{{template "base.html" .}}
{{define "title"}}
<title>课程管理</title>
{{end}}
{{define "content"}}
<h3>课程管理</h3>
{{end}}
class.html
{{template "base.html" .}}
{{define "content"}}
<h3>班级管理</h3>
{{end}}
{{define "title"}}
<title>班级管理</title>
{{end}}
main.go
package main
import (
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
"net/http"
)
func index(c *gin.Context) {
c.HTML(http.StatusOK, "index", gin.H{})
}
func student(c *gin.Context) {
c.HTML(http.StatusOK, "student", gin.H{})
}
func course(c *gin.Context) {
c.HTML(http.StatusOK, "course", gin.H{})
}
func class(c *gin.Context) {
c.HTML(http.StatusOK, "class", gin.H{})
}
func createMyRender() multitemplate.Renderer {
r := multitemplate.NewRenderer()
r.AddFromFiles("index", "templates/base.html", "templates/index.html")
r.AddFromFiles("student", "templates/base.html", "templates/student.html")
r.AddFromFiles("course", "templates/base.html", "templates/course.html")
r.AddFromFiles("class", "templates/base.html", "templates/class.html")
return r
}
func main() {
router := gin.Default()
// 返回一个html页面
// router.LoadHTMLGlob("templates/*") // 继承会发生block覆盖
router.HTMLRender = createMyRender()
router.GET("/", index)
router.GET("/student", student)
router.GET("/course", course)
router.GET("/class", class)
router.Run() // 监听并在 0.0.0.0:8080 上启动服务
}