【Go実践】テンプレートでループを使用し一覧リストを作成する

シェアする

ここまでの記事で、ユーザ情報のマスタテーブルへレコード登録するフォームを実装してきました。
以下の記事で、HTTPリクエストの処理、フォームからPOSTされた情報の取り扱い、データベース操作について解説しています。
【Go実践】POSTされたデータを受け取って処理する ― 入力フォームと確認画面の作成
【Go実践】フォームで入力された情報をデータベースに登録する

今回は、データベースから登録済のレコードを取得し、一覧リストを出力する方法をサンプルを交えて解説します。

テンプレートのループ機能

html/templateには、テンプレート側でループ処理を行う機能が提供されています。
登録済み情報の一覧表示などでは、このループ機能を使うのが良いでしょう。

{{ range . }}
(繰り返し出力したい内容)
{{ end }}

上記のように、マップやスライス等から受け取る{{ range . }} ~ {{ end }}で囲うことで、テンプレートに流し込んだマップ等のデータ全件を先頭から順に繰り返し出力することができます。

テンプレートを記述する

それでは、ループ機能を使ってユーザ一覧を出力するテンプレートを作成してみましょう。
templatesディレクトリにuser-list.gtplファイルを作成し、以下のように記述してください。

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="../asset/css/style.css">
</head>
<body>
  <h2>ユーザ一覧</h2>
  <table class="list">
    <tr>
      <th>ID</th>
      <th>アカウント名</th>
      <th>ユーザ名</th>
      <th>登録日時</th>
    </tr>
    {{ range . }}
    <tr>
      <td>{{.Id}}</td>
      <td>{{.Account}}</td>
      <td>{{.Name}}</td>
      <td>{{.Created}}</td>
    </tr>
    {{ end }}
  </table>
  </form>
</body>
</html>

15-22行目がループ処理を行っている箇所です。
{{ range . }} ~ {{ end }}で囲われた内容が、レコード件数分出力される想定です。
内容は、テーブルのデータ行を繰り返し出力し、各カラムの内容にマップの項目を指定しています。

なお、テーブルにlistというクラスを指定していますが、このクラスに対してstyle.cssで以下のようなスタイルを記述しています。
スタイルの内容についてはお好みに合わせて任意に書き換えてください。

/* -------------------- *
 * 一覧リストのスタイル
 * ------ */
table.list {
    width: 100%;
    border-collapse:separate;
    border-spacing: 0;
    border: 1px solid #3c6690;
  }
  
  table.list th{
    text-align: center;
    color:white;
    background: linear-gradient(#829ebc,#225588);
    /*border: 1px solid #3c6690;*/
    border-width: 1px 0px;
    border-color: #3c6690;
    border-style: solid;
    padding: 10px 0;
  }
  
  table.list td{
    text-align: center;
    /*border: 1px solid #a8b7c5;*/
    border-width: 1px 0px;
    border-color: #a8b7c5;
    border-style: solid;
    border-top:none;
    padding: 10px 0;
  }

テンプレートを出力する関数を記述する

続いて、リクエストハンドラとして使用する関数を実装します。
ユーザ情報マスタからレコード全件を取得し、先ほどのテンプレートに流入します。

req_handlerディレクトリにh_UserList.goファイルを作成し、以下のように記述してください。

package req_handler // 独自のHTTPリクエストハンドラパッケージ

import (
    "database/sql"
    "fmt"
    "html/template"
    "net/http"

    "../conf"  // 実装した設定パッケージの読み込み
    "../query" // 実装したクエリパッケージの読み込み

    // 実装したユーティリティパッケージの読み込み
    _ "github.com/go-sql-driver/mysql"
)

// 登録済ユーザ一覧画面
func HandlerUserList(w http.ResponseWriter, req *http.Request) {

    // テンプレートをパースする
    tpl := template.Must(template.ParseFiles("templates/user-list.gtpl"))

    // 設定ファイルを読み込む
    confDB, err := conf.ReadConfDB()
    if err != nil {
        fmt.Println("設定ファイルの読み込みに失敗しました。")
    }

    // 設定値から接続文字列を生成
    conStr := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", confDB.User, confDB.Pass, confDB.Host, confDB.Port, confDB.DbName, confDB.Charset)

    // データベース接続
    db, err := sql.Open("mysql", conStr)
    if err != nil {
        fmt.Println("データベース接続に失敗しました。")
    }
    // deferで処理終了前に必ず接続をクローズする
    defer db.Close()

    // ユーザマスタの全行データ取得関数を実行
    userlist, err := query.SelectUserAll(db)

    // マップを展開してテンプレートを出力する
    if err := tpl.ExecuteTemplate(w, "user-list.gtpl", userlist); err != nil {
        fmt.Println(err)
    }
}

基本的な処理の流れは、これまで実装してきたリクエストハンドラ関数と同じです。
テンプレートに流入するデータは独自に定義したユーザ情報構造体型のスライスになっています。

なお、ユーザマスタテーブルからレコード全件を取得するSelectUserAll()関数の実装は以下のようになっています。

package query // 独自のクエリパッケージ

import (
    "database/sql"

    _ "github.com/go-sql-driver/mysql"
)

// マスタからSELECTしたデータをマッピングする構造体
type M_USER struct {
    Id      string `db:"ID"`       // ID
    Account string `db:"ACCOUNT"`  // アカウント名
    Name    string `db:"NAME"`     // ユーザ名称
    Passwd  string `db:"PASSWORD"` // パスワード
    Created string `db:"CREATED"`  // 登録日
}

// 全行SELECT用の構造体配列
type UserList []M_USER

・
・
・
<中略>
・
・
・

// 全行データ取得関数
func SelectUserAll(db *sql.DB) (userlist UserList, err error) {

    // 配列宣言
    var ul UserList

    // プリペアードステートメント
    stmt, err := db.Prepare("SELECT ID,ACCOUNT,NAME,PASSWORD,CREATED FROM M_USER")
    if err != nil {
        return ul, err
    }

    // クエリ実行
    rows, err := stmt.Query()
    if err != nil {
        return ul, err
    }
    defer rows.Close()

    // SELECTした結果を構造体にマップ
    for rows.Next() {
        // 構造体宣言
        var user M_USER
        err = rows.Scan(&user.Id, &user.Account, &user.Name, &user.Passwd, &user.Created)
        // 配列にScan結果を追加
        ul = append(ul, user)
    }

    // 取得データをマッピングしたM_USER構造体配列を返す
    return ul, nil
}

単一行を取得する場合と異なり、SELECT結果を一行ずつループ処理で構造体に格納し、構造体型のスライスをreturnしている点に留意してください。
詳細は以下の記事をご参照ください。

【Go実践】GoでMySQLを使おう(2) – 基本的なテーブル操作と構造体へのマッピング

メイン処理にリクエストハンドラを追加する

最後に、/user-listへのHTTPリクエストハンドラを追加し、実装したハンドラ関数に対応させます。

package main

import (
    "net/http"

    "./req_handler" // 実装したHTTPリクエストハンドラパッケージの読み込み
)

func main() {

    // "user-list"へのリクエストを関数で処理する
    http.HandleFunc("/user-list", req_handler.HandlerUserList)

    // "user-form"へのリクエストを関数で処理する
    http.HandleFunc("/user-form", req_handler.HandlerUserForm)

    // "user-confirm"へのリクエストを関数で処理する
    http.HandleFunc("/user-confirm", req_handler.HandlerUserConfirm)

    // "user-registered"へのリクエストを関数で処理する
    http.HandleFunc("/user-registered", req_handler.HandlerUserRegistered)

    // css・js・イメージファイル等の静的ファイル格納パス
    http.Handle("/asset/", http.StripPrefix("/asset/", http.FileServer(http.Dir("asset/"))))

    // サーバーを起動
    http.ListenAndServe(":8080", nil)
}

リクエストハンドラを追記したら、main.goを実行し、ブラウザでhttp://localhost:8080/user-listにアクセスしてください。
下の画像のように、登録済のレコード全件がリスト表示されれば想定通りです。

終わりに

今回は、テンプレートのループ機能を使用して一覧リストを実装しました。
テンプレートのループ機能は、基本的には今回のようにテーブルやセレクトボックス等のデータ行出力に使用するのが一般的です。
デザイン側の記述が複雑にならないよう、なるべく多用は避けるようにしてください。

次回の記事では、テンプレートの分岐機能を使用し、同じテンプレートを使用して新規登録と登録済情報の編集を行う方法を解説したいと思います。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク