Golangでは標準にwebサーバーを立てるパッケージ(net/http)が存在します。webサーバーが立てられるとわかればhtmlファイルを表示したくなるのは自然だと思います。ですので、Golangでも標準ライブラリにhtmlをプログラム的に作成して表示させることができるものがあります。(さすがGolang!!)
今回は、このtemplateパッケージを利用してgolangの構造体の配列をリストレンダリングするとこまでやってみます。(本記事はgolangの環境構築ができる前提で話をします。)
今回実装する画面
今回は以下のような気温のデータの表を二つ(沖縄県と東京都)画面に表示します。
(画面の動き)
また、トップページ(/)とデータページ(/data/)の二画面を実装し、リンクで動けるようにしています。
実装の手順
実装は以下のような手順で行います。冒頭でもお伝えしたとおりgolangの環境構築ができる前提で話をします。
- サーバーを立てる準備をする
- トップページを表示できるようにする
- 表示するデータを作成する
- データのタイトルを表示する
- データの数値をリストレンダリングする
また、今回作成する画面のソースコードはcode-databaseのgithubリポジトリに上げているので、手元で確認したい方はぜひ活用してみてください。先に全体のコードを確認しておくのも手かもしれません。
実装してみる
それでは早速実装してみましょう。
サーバーを立てる準備をする
まずはローカルサーバーを立てましょう。ローカルサーバーの立ち上げにはnet/httpパッケージのListenAndServeメソッドを用います。
http.ListenAndServe(":8000", nil)
今回はport番号8000で立ち上げましょう。さらに、"/"のパスに対してトップページを返すハンドラを渡します。main関数は以下のようになります。
func main() { http.HandleFunc("/", mainHandler) http.ListenAndServe(":8000", nil) }
これでサーバーを立ち上げてレスポンスを返す準備ができました。
トップページを表示できるようにする
次にmainHandler関数内でトップページを表示する記述をしていきましょう。
トップページでは以下のようなhtmlファイルを出力します。
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>golang_template</title> </head> <body> <h1>トップページ</h1> <a href='/data/'>データを見る</a> </body> </html>
タイトル(<h1>トップページ</h1>)とデータページへのアンカー(<a href='/data/'>データを見る</a>)を用意しています。
これをtemplateパッケージを用いて表示します。
func mainHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("index.html") if err != nil { panic(err.Error()) } if err := t.Execute(w, nil); err != nil { panic(err.Error()) } }
まずはtemplate.ParseFiles()メソッドでindex.htmlを用いたテンプレートの作成をします。
func (t *Template) ParseFiles(filenames ...string) (*Template, error)
template.ParseFiles()メソッドは引数にファイル名をとり(複数のテンプレートを作成する場合は全て)*Template型のテンプレートを返します。
次にExecute()メソッドで作成したテンプレートを表示します。
func (t *Template) Execute(wr io.Writer, data interface{}) error
Execute()メソッドは引数で指定したwr(io.Writerインターフェイスを満たす)にdata(テンプレートに組み込むgolangの変数)を組み込まれたファイルを書き込むメソッドです。今回の場合、第一引数にはレスポンスを定義するwを指定します。また、テンプレートに書き込む変数がないので第二引数のdataはnilにします。
template.ParseFiles()メソッド、template.ParseFiles()はそれぞれ処理に失敗するとerrを返すようになっているので、それぞれのerrに対してエラーハンドリングしておきます。
この状態で正しく動くか検証してみます。
(ターミナル)
go run main.go
このように表示されていれば大丈夫です。("/data/"に対するハンドリングはまだ定義していないので機能しなくても大丈夫です)
表示するデータを作成する
次に表示するデータを作っておきましょう。今回は「気温と雨量の統計」で取得したデータを使用します。
表にするので、表のタイトル(label)と実際のデータが必要になります。したがって以下のような構造のデータにしましょう。
//TemperatureDataElem 気温データの一つのデータセット type TemperatureDataElem struct { Label string Data []float64 }
そして、データを実際に作成します。まずは、"/data/"パスに対するハンドラ関数の準備をしましょう。やり方はmainHandlerと同様です。
func main() { http.HandleFunc("/", mainHandler) http.HandleFunc("/data/", dataHandler) //追記 http.ListenAndServe(":8000", nil) }
dataHandler()内部には先ほど定義したもののデータセットを用意しておきましょう。
func dataHandler(w http.ResponseWriter, r *http.Request) { var temperatureData []TemperatureDataElem temperatureData = append(temperatureData, TemperatureDataElem{ Label: "沖縄県", Data: []float64{17.0, 17.1, 18.9, 21.4, 24.0, 26.8, 28.9, 28.7, 27.6, 25.2, 22.1, 18.7}, }) temperatureData = append(temperatureData, TemperatureDataElem{ Label: "東京都", Data: []float64{5.2, 5.7, 8.7, 13.9, 18.2, 21.4, 25.0, 26.4, 22.8, 17.5, 12.1, 7.6}, }) }
append関数を用いてデータを作成します。
これで長さ2のtempertureDataというデータが完成しました。それではこのデータを画面に表示していきましょう!
データをテンプレートに渡す
今回はトップページと異なりテンプレートにデータを渡すのでその部分の記述をしなくてはなりません。(と言ってもgoファイルにはほんの一言加えるだけ)
data.htmlファイルを元にテンプレート(t)を作成したらt.Execute()の際の第二引数に先ほど作成したtempertureDataを入れるだけです。以上!。
func dataHandler(w http.ResponseWriter, r *http.Request) { var temperatureData []TemperatureDataElem temperatureData = append(temperatureData, TemperatureDataElem{ Label: "沖縄県", Data: []float64{17.0, 17.1, 18.9, 21.4, 24.0, 26.8, 28.9, 28.7, 27.6, 25.2, 22.1, 18.7}, }) temperatureData = append(temperatureData, TemperatureDataElem{ Label: "東京都", Data: []float64{5.2, 5.7, 8.7, 13.9, 18.2, 21.4, 25.0, 26.4, 22.8, 17.5, 12.1, 7.6}, }) t, err := template.ParseFiles("data.html") if err != nil { panic(err.Error()) } if err := t.Execute(w, temperatureData); err != nil { panic(err.Error()) } }
データをリストレンダリングする(range関数)
次にdata.htmlを用意して、ここに渡されたデータの表示を記述していきます。今回のデータセットには東京都と沖縄県の二つのデータが入っているので、ループ処理でプログラム的に記述しましょう。この際にはrangeという関数を使用しますが、使い方はVue.jsのv-forディレクティブやDjangoの{% for elem in elems %}とほとんど同じです(それ以外の言語やフレームワークは手を触れていないので分かりませんが、たぶんRoRは同じような感じだったと思います)。下のように記述します。
<body> <h1>データページ</h1> <a href='/'>トップへ戻る</a> <ul> {{ range . }} <li> <h4>{{ .Label }}の気温</h4> {{ range .Data }} <p style="display: inline;">{{ . }}度</p> {{ end }} </li> {{ end }} </ul> </body>
リストレンダリングをしたい部分について{{ range }}という記述をし、どのデータをループさせるのかをそのあとの"."で表しています。これは今回data.htmlに渡したデータが一つだけ(tempertureData)なのでドットだけの表記になっています。{{ range }}は{{ end }}で閉じた部分までをrangeのあとのデータ(今回は".")の長さ(今回は2)の分だけ繰り返しレンダリングします。range内では配列ないのそれぞれの要素についてドットを用いて参照できます。
データのタイトルを表示する(テンプレート構文)
繰り返し表示についてはrange関数を用いることでできることがわかりました。ではデータを表示しましょう。下のように{{}}で囲むと表示できます。以上!
<p style="display: inline;">{{ . }}度</p>
これで全ての実装が完了しました。この状態で実行すればできているはずです。
(ターミナル)
go run main.go
"/data/"パスにアクセスした際に上のように表示されれば完璧です!
実装に使用したコード全体
今回使用したコードの全体を紹介します。
(ファイルの階層構造)
golang_template/ ├ index.html ├ data.html └ main.go
(main.go)
package main import ( "net/http" "text/template" ) //TemperatureDataElem 気温データの一つのデータセット type TemperatureDataElem struct { Label string Data []float64 } func mainHandler(w http.ResponseWriter, r *http.Request) { t, err := template.ParseFiles("index.html") if err != nil { panic(err.Error()) } if err := t.Execute(w, nil); err != nil { panic(err.Error()) } } func dataHandler(w http.ResponseWriter, r *http.Request) { var temperatureData []TemperatureDataElem temperatureData = append(temperatureData, TemperatureDataElem{ Label: "沖縄県", Data: []float64{17.0, 17.1, 18.9, 21.4, 24.0, 26.8, 28.9, 28.7, 27.6, 25.2, 22.1, 18.7}, }) temperatureData = append(temperatureData, TemperatureDataElem{ Label: "東京都", Data: []float64{5.2, 5.7, 8.7, 13.9, 18.2, 21.4, 25.0, 26.4, 22.8, 17.5, 12.1, 7.6}, }) t, err := template.ParseFiles("data.html") if err != nil { panic(err.Error()) } if err := t.Execute(w, temperatureData); err != nil { panic(err.Error()) } } func main() { http.HandleFunc("/", mainHandler) http.HandleFunc("/data/", dataHandler) http.ListenAndServe(":8000", nil) }
(index.html)
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>golang_template</title> </head> <body> <h1>トップページ</h1> <a href='/data/'>データを見る</a> </body> </html>
(data.html)
<!DOCTYPE html> <html lang='en'> <head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>golang_template</title> </head> <body> <h1>データページ</h1> <a href='/'>トップへ戻る</a> <ul> {{ range . }} <li> <h4>{{ .Label }}の気温</h4> {{ range .Data }} <p style="display: inline;">{{ . }}度</p> {{ end }} </li> {{ end }} </ul> </body> </html>
この記事のまとめ
本記事ではGolangのtemplateパッケージを用いてhtmlファイルの表示について説明しました。最後の内容の要点をまとめておきます。
- Golangではtemplateパッケージを用いてhtmlファイルの表示が簡単にできる
- テンプレートにはgoファイルの変数を組み込むこともできる
- htmlファイル内でプログラム的に要素の記述ができる
皆さんもぜひGoでブログとか作ってみましょう!