Go 言語って標準ライブラリにテンプレートまであるのか。便利。
ということで使ってみましょう。
使い方は簡単。template.Must(template.ParseFiles("templateFilePath1", "templateFilePath2", ...) みたいにしてテンプレートをパースする。パースしたら ExecuteTemplate メソッドで出力先とテンプレート名とテンプレートに渡す値を指定して完成。
package main import ( "html/template""net/http" ) type pageData struct { Title string Message string } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{Title: "Template sample title", Message: "Template sample message"}) }) http.ListenAndServe("localhost:8080", mux) }
テンプレートは以下のような感じになります。
まずは templates/index.html
{{ define "index" }} <!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html> {{ end }}
templates/test.html はこんな感じ。
{{ define "test" }} <!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Test</title></head><bodystyle="background-color: yellow"><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html> {{ end }}
基本的には {{ }}
でくくった中に何か書く。先頭は define でレイアウト名(ExecuteTemplate で指定する名前)を指定して最後に end でおしまい。
{{ .プロパティ名 }} とかで ExecuteTemplate で渡されたデータにアクセスできる感じっぽい。
動かしてみるとちゃんと動いた。
今回のプログラムは URL のパラメータでテンプレート名指定するからこんな感じでも動く。
{{ range .xxx }} でループも行ける。テンプレートをいじって
{{ define "index" }} <!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul> {{ range .Records }} <li>{{ .Name }}</li> {{ end }} </ul></body></html> {{ end }}
main.go もそれにあわせて変更。
package main import ( "html/template""net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "Template sample message", Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
いい感じに動く。
さらに if やカスタムの関数とかも定義出来るみたい。今回は偶数番目のデータと奇数番目のデータで色を変えたかったので、mod という関数を追加したうえでパースして実行してみました。
package main import ( "html/template""net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "Template sample message", Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
テンプレートはこんな感じになりました。
{{ define "index" }} <!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul> {{ range $index, $record := .Records }} {{ $isEvenRow := eq (mod $index 2) 0 }} <listyle="color: {{ if $isEvenRow}} red {{ else }} black {{ end }}">{{ $index }}{{ $record.Name }}</li> {{ end }} </ul></body></html> {{ end }}
if や range のインデックスの取得方法と関数呼び出しと盛りだくさん。実行するとこんな感じです。
あとはサニタイズとかもあるみたいですね。普通に Go 側とこういう風に書くと…
package main import ( "html/template""net/http" ) type record struct { Name string } type pageData struct { Title string Message string Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: "<script type='text/javascript`>alert('Template sample message')</script>", // !? Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
素晴らしい。
あえてエスケープしたくないときは template.HTML を使う。まぁレアケースだけどマークダウンエディターみたいなものを作りたいときとかは必要。
こんな感じで
package main import ( "html/template""net/http" ) type record struct { Name string } type pageData struct { Title string Message template.HTML Records []record } func main() { templateFiles := []string{"templates/index.html", "templates/test.html"} templates := template.Must(template.New("").Funcs(template.FuncMap{ "mod": func(x int, y int) int { return x % y }, }).ParseFiles(templateFiles...)) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { templateName := "index" templateNames, ok := req.URL.Query()["template"] if ok { templateName = templateNames[0] } templates.ExecuteTemplate(w, templateName, pageData{ Title: "Template sample title", Message: template.HTML("<script type='text/javascript'>alert('Template sample message')</script>"), Records: []record{ record{Name: "aaaaaaaa"}, record{Name: "bbbbbbbb"}, record{Name: "cccccccc"}, record{Name: "dddddddd"}, record{Name: "eeeeeeee"}, }, }) }) http.ListenAndServe("localhost:8080", mux) }
実行するとこうなる。ばっちり。