03-Template-Advance
学习完 第二章 之后,你对模板已经有了基本的认识
本章将讨论 Go 的组合特性,以及 建立一个通用的调用 模板的方法
本章的GitHub链接为: Source, Diff, Zip

匿名组合

匿名组合 其实是Go里的一个非常重要的特性,在 Go 的世界里没有继承,只有组合(当然还有接口)。组合其实可以实现部分的继承。
main.go
1
type Post struct {
2
User
3
Body string
4
}
5
6
// IndexViewModel struct
7
type IndexViewModel struct {
8
Title string
9
User
10
Posts []Post
11
}
Copied!
就是 将 User User -> User其它都不变,这样执行,发现程序照常运行
不过,现在我们可以改下templates/index.html
templates.index.html
1
<html>
2
<head>
3
{{if .Title}}
4
<title>{{.Title}} - blog</title>
5
{{else}}
6
<title>Welcome to blog!</title>
7
{{end}}
8
</head>
9
<body>
10
<h1>Hello, {{.Username}}!</h1>
11
{{range .Posts}}
12
<div><p>{{ .Username }} says: <b>{{ .Body }}</b></p></div>
13
{{end}}
14
15
</body>
16
</html>
Copied!
由于 匿名组合 ,我们现在可以将 {{.User.Username}} -> {{.Username}}
就是我们可以直接使用 匿名组合 的属性,以及方法,其实也是变像的实现了继承。
关于 Go 的 面向对象,可以看下 参考
本小节 Diff

模板继承

其实 Go 的模板应该没有 Flask jinja2 这样的功能强大,它只有 include,所以为了实现模板的继承,我们需要发挥下主观能动性
main.go
1
package main
2
3
import (
4
"html/template"
5
"io/ioutil"
6
"net/http"
7
"os"
8
)
9
10
// User struct
11
type User struct {
12
Username string
13
}
14
15
// Post struct
16
type Post struct {
17
User
18
Body string
19
}
20
21
// IndexViewModel struct
22
type IndexViewModel struct {
23
Title string
24
User
25
Posts []Post
26
}
27
28
// PopulateTemplates func
29
// Create map template name to template.Template
30
func PopulateTemplates() map[string]*template.Template {
31
const basePath = "templates"
32
result := make(map[string]*template.Template)
33
34
layout := template.Must(template.ParseFiles(basePath + "/_base.html"))
35
dir, err := os.Open(basePath + "/content")
36
if err != nil {
37
panic("Failed to open template blocks directory: " + err.Error())
38
}
39
fis, err := dir.Readdir(-1)
40
if err != nil {
41
panic("Failed to read contents of content directory: " + err.Error())
42
}
43
for _, fi := range fis {
44
func() {
45
f, err := os.Open(basePath + "/content/" + fi.Name())
46
if err != nil {
47
panic("Failed to open template '" + fi.Name() + "'")
48
}
49
defer f.Close()
50
content, err := ioutil.ReadAll(f)
51
if err != nil {
52
panic("Failed to read content from file '" + fi.Name() + "'")
53
}
54
tmpl := template.Must(layout.Clone())
55
_, err = tmpl.Parse(string(content))
56
if err != nil {
57
panic("Failed to parse contents of '" + fi.Name() + "' as template")
58
}
59
result[fi.Name()] = tmpl
60
}()
61
}
62
return result
63
}
64
65
func main() {
66
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
67
u1 := User{Username: "bonfy"}
68
u2 := User{Username: "rene"}
69
70
posts := []Post{
71
Post{User: u1, Body: "Beautiful day in Portland!"},
72
Post{User: u2, Body: "The Avengers movie was so cool!"},
73
}
74
75
v := IndexViewModel{Title: "Homepage", User: u1, Posts: posts}
76
77
templates := PopulateTemplates()
78
templates["index.html"].Execute(w, &v)
79
})
80
http.ListenAndServe(":8888", nil)
81
}
Copied!
templates/_base.html
1
<html>
2
<head>
3
{{if .Title}}
4
<title>{{.Title}} - blog</title>
5
{{else}}
6
<title>Welcome to blog!</title>
7
{{end}}
8
</head>
9
<body>
10
<div>Blog: <a href="/">Home</a></div>
11
{{template "content" .}}
12
</body>
13
</html>
Copied!
templates/content/index.html
1
{{define "content"}}
2
<h1>Hello, {{.User.Username}}!</h1>
3
4
{{range .Posts}}
5
<div><p>{{ .User.Username }} says: <b>{{ .Body }}</b></p></div>
6
{{end}}
7
{{end}}
Copied!
这里用了模板继承,_base 是 基础模板,这样 比如 head 等信息不用再重复的去在每个.html文件中重复定义,我们可以专注于每个页面的业务逻辑和内容。
由于没有像 Jinja2 这样的原生支持模板继承,这个实现的关键就是 PopulateTemplates 函数,它的作用是 遍历 templates/content/ 文件夹下的所有文件,并和 templates/_base.html 合成 template.Template,然后再存入 map 中(在 Python 中一般叫 dict),可以使用例如 index.html 的 key 来访问。
我们现在运行下程序,页面还是和原来一样(只是我们在 _base template 里面加入了 Home 的导航),不过我们的templates文件夹已经有了基础模板,并且具备了快速扩展的能力。下章 Web Form 我们可以看见效果。
03-01
本小节 Diff

Links