HTMLテンプレート
テンプレートエンジンは動的にHTMLを生成する仕組みです。
HTMLに変数を埋め込み、HTMLの生成に条件分岐や繰り返しの制御をすることで動的なコンテンツを提供します。
Goの標準ライブラリではテンプレートエンジンとしてhtml/template
パッケージが提供されています。
テンプレートエンジンの使い方
まずは小さなサンプルとその実行結果を見てみましょう。
package main
import (
"html/template"
"os"
)
func main() {
tpl := `<p>{{ . }}</p>`
data := "Hello"
t := template.Must(template.New("a").Parse(tpl))
t.Execute(os.Stdout, data)
}
<p>Hello</p>
このサンプルでは次の処理をしています。
template.New
関数でテンプレートを作成するtemplate.Template.Parse
関数でテンプレートを解析するtemplate.Must
関数はParse
関数が失敗したらパニックになるよう振る舞いを変更するtemplate.Template.Execute
メソッドでテンプレートにデータを埋め込む
テンプレートの中の{{}}
で括られた部分は、特別な意味があります。
{{}}
の中の.
はExecute
関数で指定したデータを指します。
引数の書き方
引数は単純な値のことです。
先ほど、データを埋め込む箇所に.
と記述しましたが、これを引数と呼びます。
他にもマップのキーや構造体のフィールドを指定できます。
マップのキーは次の通り指定します。
テンプレート
<p>{{ .key1 }}</p>
データ
map[string]string{"key1": "val1", "key2": "val2"}
実行結果
<p>val1</p>
構造体のフィールドは次の通り指定します。
テンプレート
<p>{{ .Field1 }}</p>
データ
struct {
Field1 string
Field2 string
}{
Field1: "val1",
Field2: "val2",
}
実行結果
<p>val1</p>
アクションの書き方
アクションはデータの評価やフロー制御をします。
条件分岐は次の通り記述します。
テンプレート
{{ if .key1 }}
<p>a</p>
{{ else if .key2 }}
<p>b</p>
{{ else }}
<p>c</p>
{{ end }}
データ
map[string]bool{"key1": false, "key2": true}
実行結果
<p>b</p>
繰り返しは次の通り記述します。
テンプレート
{{ range . }}
<p>{{ . }}</p>
{{ end }}
データ
map[string]string{"key1": "val1", "key2": "val2"}
実行結果
<p>val1</p>
<p>val2</p>
繰り返すデータがない場合の処理は次の通り記述します。
テンプレート
{{ range . }}
<p>{{ . }}</p>
{{ else }}
<p>データなし</p>
{{ end }}
データ
map[string]string{}
実行結果
<p>データなし</p>
変数の書き方
テンプレートの中で変数を定義できます。
変数の宣言と初期化は次の通り記述します。
テンプレート
{{ $v := . }}<p>{{ $v }}</p>
データ
"Hello"
実行結果
<p>Hello</p>
rangeアクションでは次の通り記述します。
テンプレート
{{ range $k, $v := . }}
<p>{{ $k }}: {{ $v }}</p>
{{ end }}
データ
map[string]string{"key1": "val1", "key2": "val2"}
実行結果
<p>key1: val1</p>
<p>key2: val2</p>
関数の書き方
テンプレートの中で関数を実行できます。
printf関数を実行して数字をゼロ埋めするには、次の通り記述します。
テンプレート
<p>{{ printf "%03d" . }}</p>
データ
1
実行結果
<p>001</p>
テンプレートファイルの読み込み
次はテンプレートをファイルから読み込むサンプルです。これまでのParse
メソッドの代わりにParseFiles
メソッドを使います。
package main
import (
"html/template"
"os"
)
func main() {
data := "Hello"
t := template.Must(template.New("a.html").ParseFiles("a.html"))
t.Execute(os.Stdout, data)
}
サンプルではtemplate.New
関数とtemplate.Template.ParseFile
メソッドを組み合わせて使用しました。
これをもっと短く書くためにはtemplate.ParseFiles
関数が使えます。
// この2行は同等の処理
t := template.Must(template.New("a.html").ParseFiles("a.html"))
t := template.Must(template.ParseFiles("a.html"))
a.html
<p>{{ . }}</p>
実行結果
<p>Hello</p>
テンプレートファイルの分割
ParseFiles
は関数名の通り複数のファイルを読み込めます。
HTMLの各ページで共通なヘッダやフッタなどのレイアウトと各ページ固有のコンテンツを分割できます。
package main
import (
"html/template"
"os"
)
func main() {
data := "メインコンテンツ"
t := template.Must(template.ParseFiles("layout.html", "page1.html"))
t.Execute(os.Stdout, data)
}
共通レイアウトのテンプレートからページ固有コンテンツのテンプレートを取り込むには、
{{ template "main" . }}
のように記述します。
templateの引数は、1つ目はテンプレート名、2つ目はテンプレートに渡すデータを指定します。
layout.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>ヘッダ</p>
{{ template "main" . }}
<p>フッタ</p>
</body>
</html>
取り込まれる側のコンテンツには、テンプレートに名前を付けます。
{{ define "main" }}テンプレート本体{{ end }}
の形式で記述します。
defineの引数はテンプレート名を指定します。
page1.html
{{ define "main" }}
<p>{{ . }}</p>
{{ end }}
実行結果
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>ヘッダ</p>
<p>メインコンテンツ</p>
<p>フッタ</p>
</body>
</html>
Webサーバでの利用
次は実際にWebサーバで利用したサンプルです。
package main
import (
"fmt"
"html/template"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
server := http.Server{
Addr: ":8080",
Handler: mux,
}
server.ListenAndServe()
}
func handler(w http.ResponseWriter, r *http.Request) {
t := template.Must(template.ParseFiles("layout.html", "page1.html"))
err := t.Execute(w, map[string]interface{}{
"Slice": []int{1, 2},
"Map": map[string]string{"k1": "v1", "k2": "v2"},
})
if err != nil {
fmt.Fprintln(w, err)
}
}
layout.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>ヘッダ</p>
{{ template "main" . }}
<p>フッタ</p>
</body>
</html>
page1.html
{{ define "main" }}
<p>スライス</p>
<ul>
{{ range .Slice }}
{{ if eq . 1 }}
<li>壱</li>
{{ else }}
<li>{{ . }}</li>
{{ end }}
{{ else }}
<li>データなし</li>
{{ end }}
</ul>
<p>マップ</p>
<ul>
{{ range $k, $v := .Map }}
<li>{{ $k }}: {{ $v }}</li>
{{ end }}
</ul>
{{ end }}
実行結果
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>ヘッダ</p>
<p>スライス</p>
<ul>
<li>壱</li>
<li>2</li>
</ul>
<p>マップ</p>
<ul>
<li>k1: v1</li>
<li>k2: v2</li>
</ul>
<p>フッタ</p>
</body>
</html>