golang の html/template について書かれたブログ等を色々見ていると、みんなレイアウトとコンテンツの分離に苦労している感があったのでどうやるか書いておきます。
t.ExecuteTemplate(w, "content", data)
Go の html/template はテンプレートの名称を指定して ExecuteTemplate を実行します。しかし html/template には、その content を囲う layout テンプレートを指定する方法がありません。特に以下の様に ParseGlob を使った場合、各 html で同じ content という名前で define する事は出来ません。
template.ParseGlob("public/views/*.html")
やりたいのは layout というテンプレートの中から content という名称で読み込まれ、その content に今回実行したいテンプレート、例えば search を適用したいはずです。
{{define "layout"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{.Title}}</title>
</head>
<body>
{{template "content" .}}
</body>
</html>
{{end}}
これを実現するには AddParseTree を使います。
template.Must(template.Must(t.templates.Lookup("layout").Clone()).AddParseTree("content", t.templates.Lookup("search").Tree)).ExecuteTemplate(w, "layout", data)
layout という名称のテンプレート(上記)をクローンし、そのパース済みツリーに content という名前で実行したい別のテンプレート search のパース済みツリーを追加します。これで、layout という名前の一時テンプレートには、content という名前で search の内容が出力される事になります。もちろん ExecuteTemplate に渡すデータには、layout テンプレートで使うタイトル文字列も渡す事になります。
template.Must(template.Must(t.templates.Lookup("layout").Clone()).AddParseTree("content", t.templates.Lookup("search").Tree)).ExecuteTemplate(w, "layout", struct {
Title string
Message string
}{
Title: "検索",
Message: "検索結果はありません",
})
ただしこの方法は、ソースコードに layout という文言が埋め込まれる事になり、content テンプレートの方からレイアウトテンプレートを選ぶ方法がありません。この問題を回避するには、Jekyll のテンプレートの様に、HTML ファイルのヘッダ部に YAML の様なメタ情報を書かせてテンプレート名とレイアウトのペアをプログラムで管理しておき、名称からそのペアを取り出せる仕組みを作っておけば良いですね。