はじめに
Goの標準で用意されているテンプレートエンジン(html/template)を使って、htmlのtableをレンダリングし、echoでwebサーバーを建てて表示してみました。
環境
- macOS Sonoma 14.4.1
- Go 1.22.3 darwin-arm64
表示したいもの
このようなテーブルを表示するhtmlを出力することがゴールとなります。
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<title>Table Rendering</title>
<link rel="stylesheet" href="/public/css/style.css" type="text/css">
<body>
<table>
<thead>
<tr>
<th></th>
<th>Left</th>
<th>Center</th>
<th>Right</th>
</tr>
</thead>
<tbody>
<tr>
<th>Top</th>
<td>0</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<th>Center</th>
<td>3</td>
<td>4</td>
<td>5</td>
</tr>
<tr>
<th>Bottom</th>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
</tbody>
</table>
</body>
</html>
thead {
background-color: #ffccff; /* table headerの背景色指定 */
}
tbody {
background-color: #ccffff; /* table bodyの背景色指定 */
}
th,td {
border: solid 1px; /* 枠線指定 */
}
tableへのデータを渡す方法
テンプレートエンジンにデータを格納する際、
<tbody></tbody>
において、どこで繰り返し処理をするか?を考えたとき、下記2通りのパターンを考えました。
パターン1
テンプレートは<tr></tr>
までに留めておき、その間に1行ずつ<th></th>
を1列+<td></td>
を3列分のhtmlタグ3行分格納した2次元のスライスで渡します。すなわち、4列×3行のスライスになります。
<tbody></tbody>
の中に渡したい構造体:<tr></tr>
の間に[][]template.HTML
パターン2
テンプレートで<th></th>
や<td></td>
まで作って、その間に1次元スライスのstringデータを渡します。テンプレート側で<th></th>
は1回、<td></td>
は{{range}}により3回繰り返されます。
<tbody></tbody>
の中に渡したい構造体:<th></th>
の間にstring、<td></td>
の間に[]string
実践
webサーバー立ち上げ
まず初めに、下記のコマンドでmoduleの初期化を行います。
go mod init SSR
次にmain.goを作成し、下記の通りechoでwebサーバーを建てます。
package main
import (
"SSR/model"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Static("/public/css", "./public/css") //cssファイルのアドレス指定
e.GET("/1", model.ShowTable1) //パターン1
e.GET("/2", model.ShowTable2) //パターン2
e.Logger.Fatal(e.Start(":8080")) //ポート8080でwebサーバー起動
}
データを渡すための構造体
まず構造体宣言のファイルを作成します。
Body_valの中身がパターン1及びパターン2の構造体に対応していて、<tr></tr>
の間に渡されます。
package model
import "html/template"
/* パターン1 */
type Table1 struct {
Column []string
Body_val []Body1
}
type Body1 struct {
Array [][]template.HTML //2次元スライス
}
/* パターン2 */
type Table2 struct {
Column []string
Body_val []Body2
}
type Body2 struct {
Indexdata string
Rowdata []string //1次元スライス
}
テンプレートファイルの作成
構造体を流し込むためのテンプレートを下記の通り準備しました。
Body_valの中の構造体を渡すときに、{{range $s:=.Body_val}}
として$s
に置き換えておくことで、入れ子になっている構造体の中を取り出しやすくなります。
コツとして、{{range ***}}
は繰り返したいタグの一つ上の行の最後に置き、
{{end}}
は繰り返したいタグの直後に置くと変な改行が入らず、見やすいhtmlが出力されます。
パターン1
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<title>Table Rendering</title>
<link rel="stylesheet" href="/public/css/style.css" type="text/css">
<body>
<table>
<thead>
<tr>{{range .Column}}
<th>{{.}}</th>{{end}}
</tr>
</thead>
<tbody>{{range $s:=.Body_val}}{{range $s.Array}}
<tr>{{ range .}}
{{.}}{{end}}
</tr>{{end}}{{end}}
</tbody>
</table>
</body>
</html>
パターン2
<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<title>Table Rendering</title>
<link rel="stylesheet" href="/public/css/style.css" type="text/css">
<body>
<table>
<thead>
<tr>{{range .Column}}
<th>{{.}}</th>{{end}}
</tr>
</thead>
<tbody>{{range $s:=.Body_val}}
<tr>
<th>{{$s.Indexdata}}</th>{{range $s.Rowdata}}
<td>{{.}}</td>{{end}}
</tr>{{end}}
</tbody>
</table>
</body>
</html>
tableへのレンダリング
用意した構造体にデータを格納していきます。パターン1とパターン2でBody_valへの格納の仕方を見比べて見てください。
package model
import (
"bytes"
"html/template"
"io"
"log"
"net/http"
"strconv"
"github.com/labstack/echo/v4"
)
/* パターン1 */
func ShowTable1(c echo.Context) error {
var buffer bytes.Buffer //一旦バッファーにhtmlを出力する
tpl := template.Must(template.ParseFiles("template1.html.tmpl"))
data := Table1{}
data.Column = []string{"", "Left", "Center", "Right"} //headerのカラム
body_temp := Body1{}
row := []string{"Top", "Center", "Bottom"}
for i := 0; i < 3; i++ {
htmlstring := []template.HTML{}
htmlstring = append(htmlstring, template.HTML(`<th>`+row[i]+`</th>`))
for j := 0; j < 3; j++ {
numstring := strconv.Itoa(i*3 + j)
htmlstring = append(htmlstring, template.HTML(`<td>`+numstring+`</td>`))
}
body_temp.Array = append(body_temp.Array, htmlstring) //htmlタグを格納した4列×3行の2次元スライスを渡す
}
data.Body_val = append(data.Body_val, body_temp)
err := tpl.Execute(&buffer, data)
if err != nil {
log.Print(err)
}
return c.HTML(http.StatusOK, buffer.String())
}
/* パターン2 */
func ShowTable2(c echo.Context) error {
var buffer bytes.Buffer //一旦バッファーにhtmlを出力する
tpl := template.Must(template.ParseFiles("template2.html.tmpl"))
data := Table2{}
data.Column = []string{"", "Left", "Center", "Right"} //headerのカラム
row := []string{"Top", "Center", "Bottom"}
for i := 0; i < 3; i++ {
rowdata := Body2{}
rowdata.Indexdata = row[i]
for j := 0; j < 3; j++ {
numstring := strconv.Itoa(i*3 + j)
rowdata.Rowdata = append(rowdata.Rowdata, numstring)
}
data.Body_val = append(data.Body_val, rowdata) //行ごとのデータを格納した1次元スライスを渡す
}
err := tpl.Execute(&buffer, data)
if err != nil {
log.Print(err)
}
return c.HTML(http.StatusOK, buffer.String())
}
動作確認
go mod tidy
go run main.go
とコマンドを打ち込んでwebサーバーを起動し、動作確認します。
ブラウザで下記URLを打ち込んで、上記テーブルが表示されれば完成です。
パターン1
http://localhost:8080/1
パターン2
http://localhost:8080/2