はじめに
Goの標準パッケージのhtml/template
を使い、テンプレート化できるところはテンプレート化していきます。
では、さっそくやっていきましょう!
やりたいこと
今回は、名前入力ページと名前出力ページを作成し、
2つのページの共通部分(ヘッダー&フッター)をGoの標準パッケージのhtml/template
を使い共通化します。
まずは基本から
まず、下記のようにファイルを作成します。
myApp
├── /client
│ ├── index.html.gtpl
│ └── sigin.html.gtpl
└── main.go
続いて、index.html.gtpl
とsigin.html.gtpl
を作成します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ .Title }}</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<form action="/sigin" method="POST">
<input type="text" name="name">
<button type="submit">送信</button>
</form>
<p>©{{ .Footer }}</p>
</body>
</html>
<!-- 省略 --->
<title>{{ .Title }}</title>
</head>
<body>
<h1>{{ .Title }}</h1>
<p>こんにちわ、{{ .Name }}さん。</p>
<p>©{{ .Footer }}</p>
</body>
</html>
上記の、2つのテンプレートでは、Title
、Name
、Footer
があります。
次に、main.go
を作成します。
package main
import (
"html/template"
"log"
"net/http"
)
var templates = make(map[string]*template.Template)
func init() {
templates["index"] = loadTemplate("index")
http.HandleFunc("/", index)
templates["sigin"] = loadTemplate("sigin")
http.HandleFunc("/sigin", sigin)
}
func index(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Footer string
}{
Title: "Go template Lesson",
Footer: "2020 Go template Lesson",
}
if err := templates["index"].Execute(w, data); err != nil {
log.Printf("failed to execute template: %v", err)
}
}
func sigin(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Name string
Footer string
}{
Title: "Go template Lesson",
Name: r.FormValue("name"),
Footer: "2020 Go template Lesson",
}
if err := templates["sigin"].Execute(w, data); err != nil {
log.Printf("failed to execute template: %v", err)
}
}
func loadTemplate(name string) *template.Template {
t, err := template.ParseFiles("client/" + name + ".html.gtpl")
if err != nil {
log.Fatal("ParseFiles: ", err)
}
return t
}
func main() {
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
テンプレートを読み
var templates = make(map[string]*template.Template)
func init() {
templates["index"] = loadTemplate("index")
http.HandleFunc("/", index)
templates["sigin"] = loadTemplate("sigin")
http.HandleFunc("/sigin", sigin)
}
func loadTemplate(name string) *template.Template {
t, err := template.ParseFiles("client/" + name + ".html.gtpl")
if err != nil {
log.Fatal("ParseFiles: ", err)
}
return t
}
こちらは起動時にテンプレートファイルを読み込んでいます。
値を詰める
func index(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Footer string
}{
Title: "Go template Lesson",
Footer: "2020 Go template Lesson",
}
if err := templates["index"].Execute(w, data); err != nil {
log.Printf("failed to execute template: %v", err)
}
}
あとは、パースしたテンプレートファイルに値を詰めて実行します。
今回はstruct
でやりましたが、map
でも可能です。
それでは、go run main.go
で実行し、サーバを起動してみましょう。
動きはできたと思います。
ヘッダーとフッターの共通化
それでは、本題です。
ここからヘッダーとフッターのテンプレート定義し作成します。
まずは、_header.html.gtpl
と_footer.html.gtpl
のファイルを作成します。
myApp
├── /client
│ ├── /template
│ │ ├── _header.html.gtpl
│ │ └── _footer.html.gtpl
│ ├── index.html.gtpl
│ └── sigin.html.gtpl
└── main.go
続いて_header.html.gtpl
と_footer.html.gtpl
に部品を定義します。
部品を定義するときは、
{{define "NAME"}}
〜要素〜{{end}}
で定義します。
{{ define "header" }}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{{ .Title }}</title>
</head>
<body>
<h1>{{ .Title }}</h1>
{{ end }}
{{ define "footer" }}
<p>©{{ .Footer }}</p>
</body>
</html>
{{ end }}
部品が完成しました。
最後にindex.html.gtpl
とsigin.html.gtpl
を編集します。
部品を呼び出す際は、
{{template "NAME" }}
or {{template "NAME" .}}
で呼び出します。
.
があるかないかは、値を渡しているかで変わります。
値を渡さない静的なテンプレートの場合は、{{template "NAME" }}
値を渡す動的なテンプレートの場合は、{{template "NAME" .}}
で呼び出します。
{{ template "header" . }}
<main>
<form action="/sigin" method="POST">
<input type="text" name="name" tabindex="1">
<button type="submit" tabindex="0">送信</button>
</form>
</main>
{{ template "footer" . }}
{{ template "header" . }}
<p>こんにちわ、{{ .Name }}さん。</p>
{{ template "footer" . }}
それでは最後にmain.go
を編集します。
loadTemplateの編集
func loadTemplate(name string) *template.Template {
t, err := template.ParseFiles(
"client/"+name+".html.gtpl",
"./client/template/_header.html.gtpl",
"./client/template/_footer.html.gtpl",
)
if err != nil {
log.Fatal("template ParseFiles: ", err)
}
return t
}
ParseFiles()
の引数は、可変長になっていて複数のパスを受け取ります。
最上位となるテンプレートを第一引数に渡したあと、その後ろに部品を列挙していきます。
なので部品となるテンプレートは第二引数以降に記述してください!
部品の値の構造体を定義
部品となるテンプレートの値となる構造体を作成します。
type common struct {
Title string
Footer string
}
var tmpData = common{
Title: "Go template Lesson",
Footer: "2020 Go template Lesson",
}
さいごに各ページの関数にtmpData
を渡してあげます。
func index(w http.ResponseWriter, r *http.Request) {
if err := templates["index"].Execute(w, tmpData); err != nil {
log.Printf("failed to execute template: %v", err)
}
}
func sigin(w http.ResponseWriter, r *http.Request) {
data := struct {
common
Name string
}{
common: tmpData,
Name: r.FormValue("name"),
}
if err := templates["sigin"].Execute(w, data); err != nil {
log.Printf("failed to execute template: %v", err)
}
}
index()
は定義した構造体をそのまま使えば問題ないですが、sigin()
では、構造体を継承し、formからの値を含めた構造体を定義してください。
これで再度、実行してみてください。
さいごに
今回は、共通化できる部品をテンプレート化してみました。
ある程度の規模なら楽に開発できそうですね。