以前, templateパッケージを利用する方法を調べたけど, 構成的な意味でもうちょっと実用的な例が欲しいので書き残しました.
コードはgithubに置いておきます.
https://github.com/twinbird/go-template-samplegithub.com
やりたいこと
- どのページでも使うlayoutをまとめたい
- 部品的なHTMLを使いまわしたい
例
作るもののイメージ
ブログシステムを考えてみます.
一般に2種類のページ枠があるでしょう.
もう少し具体的に以下のページを考えます.
一般公開ページと管理者ページではメニューが異なるという事にします.
挙げるとキリがないのでこの辺で.
コード
長いですが, メモなのでそれぞれ全部載せます.
テンプレートは全体的に適当です.
ディレクトリ構成
$ tree .
.
├── main.go
└── templates
├── admin_menu.html # 管理者用メニュー(define "menu")
├── entry_editor.html # 記事エディタ(define "contents")
├── entry_view.html # 記事ビュワー(define "contents")
├── layout.html # 共通レイアウト(define "layout")
└── public_menu.html # 公開用メニュー(define "menu")
1 directory, 6 files
テンプレートコード
共通レイアウト
layout.html
ここでdefine "layout"としています.
{{define "layout"}}
<html>
<head>
<title>{{.Title}}</title>
<meta charset="utf-8">
</head>
<body>
<div class="menu">
{{template "menu" .}}
</div>
<div class="container">
{{template "contents" .}}
</div>
</body>
</html>
{{end}}
管理者用メニュー
admin_menu.html
ここと公開用メニューでdefine "menu"としています.
{{define "menu"}}
<ul>
<li><a href="">New Entry</a></li>
<li><a href="">Edit Entry</a></li>
</ul>
{{end}}
公開用メニュー
public_menu.html
{{define "menu"}}
<ul>
<li><a href="">About me</a></li>
<li><a href="">Latest Entry</a></li>
</ul>
{{end}}
記事エディタ
以下2つはdefine "contents"です.
entry_editor.html
{{define "contents"}}
<textarea>{{.Entry.Text}}</textarea>
{{end}}
記事ビュワー
entry_view.html
{{define "contents"}}
<pre>
{{.Entry.Text}}
</pre>
{{end}}
goのコード
package main
import (
"fmt"
"html/template"
"net/http"
)
type RenderData struct {
Title string
Entry *Entry
}
type Entry struct {
Text string
}
var (
sampleEntry *Entry = &Entry{Text: "This is a sample entry"}
sampleData *RenderData = &RenderData{Title: "sample", Entry: sampleEntry}
templatesDir string = "templates"
)
func main() {
http.HandleFunc("/admin/edit/someEntry", editAdminSomeEntry)
http.HandleFunc("/admin/refer/someEntry", referAdminSomeEntry)
http.HandleFunc("/refer/someEntry", referSomeEntry)
http.ListenAndServe(":8080", nil)
}
func editAdminSomeEntry(w http.ResponseWriter, r *http.Request) {
execTemplate(w, sampleData, "layout", "admin_menu", "entry_editor")
}
func referAdminSomeEntry(w http.ResponseWriter, r *http.Request) {
execTemplate(w, sampleData, "layout", "admin_menu", "entry_view")
}
func referSomeEntry(w http.ResponseWriter, r *http.Request) {
execTemplate(w, sampleData, "layout", "public_menu", "entry_view")
}
func execTemplate(w http.ResponseWriter, data interface{}, files ...string) {
var pathes []string
for _, f := range files {
p := fmt.Sprintf("%s/%s.html", templatesDir, f)
pathes = append(pathes, p)
}
template := template.Must(template.ParseFiles(pathes...))
template.ExecuteTemplate(w, "layout", data)
}
テンプレートをキャッシュするか
init時に全てのテンプレートをParseしてmapなどに保存しておくことが出来るはずです.
今回のexecTemplate関数をmapからテンプレートを取得し, パッケージ標準のExecuteTemplateを呼び出す仕組みに変えるだけで高速化もできそうです.
(どの程度早くなるかはわかりませんが)