SpringBoot

Thymeleaf を使ったView層の開発

2020-06-21 中村

Thymeleafは,SpringBootで推奨されているテンプレートエンジンである.
サーバサイドで画面 (View層)を生成することができる.

REST-APIを呼び出して,JavaScriptで動的に画面を書き換えるやり方とは
アプローチが異なることに注意.

テンプレートエンジンとは

アプリケーションは,あらかじめ画面のひな型となるHTML
(テンプレート)を用意しておく.

テンプレートは,ところどころ穴あき(変数)のHTMLになっており,
@ControllerがDTOとテンプレートを指定すれば,ThymeleafがDTOの
フィールド値をテンプレートの変数に代入して穴埋めし,完全なHTML
にレンダリングしてブラウザに返却する.

Thymeleaf の概要

thymeleaf.png

@Controllerオブジェクト(@RestControllerではない)が処理結果を
含んだDTOと画面のひな型となるHTMLテンプレートを指示すれば,
ThymeleafがDTOの値をテンプレートの指定された場所に組み込んで
レンダリングする.

テンプレートは,Spring Boot プロジェクトのresources/templates/
の下に,Thymeleafの変数を埋め込んだHTML形式で保存する.

なお,Spring Boot プロジェクトは,変数を含まないHTML(静的なWeb
ページ)を持つこともでき,その場合には,resources/static/ の下に
保存する.

1. 準備

build.gradle に依存性を確認

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

2. 画面設計

2.1 画面と画面遷移

コーディングする前に,まずは画面と画面遷移を大まかでよいので, ノート等にスケッチしておくとよいだろう.

画面

画面遷移

画面遷移の処理の流れを,最初の図を参照しながら,確認しよう

thymeleaf.png

  1. ユーザがブラウザ上のボタンを押す
  2. ControllerにGETまたはPOSTが発行される
  3. ControllerがServiceを経由してビジネスロジックを実行し,結果を得る
  4. Controllerが結果をModelに属性としてセットする.addAttribute()
  5. Controllerがreturnでテンプレートを文字列で指定する(例えばhogeとする)
  6. Thymeleafがtemplatesの中からテンプレート文字列.html (hoge.html)を探しだす.
  7. Thymeleafがhoge.htmlにModelの属性値を流しこみ完全なHTMLを生成して,クライアントに返す
  8. ユーザのブラウザ上に画面が現れる

2.2 例題:健康診断Webアプリ

健康診断アプリ(ソフトウェア工学第4回課題)の画面設計を行う

画面の例

screens.png

画面遷移の例

transition.png

3. @Controller クラスの構成

サーバサイドで画面を構成する場合,@RestController クラスではなく,
@Controller クラスを定義する (両方あっても構わないが,URLパスが
重ならないように気を付けること)

PersonController.java

@Controller
public class PersonController {
    @Autowired
    PersonManager pm; //サービスクラス

  (以降メソッド定義)

}

各メソッドの定義について,2.2の例題にそって説明する.

3.1 受診者リストの表示 (GET /persons)

登録されているすべての受診者のデータを画面に表示する

  1. Serviceに問い合わせて,全受診者のリストを取得し personListとする
  2. 画面モデルに,属性plistを定義し,personListを紐づける
  3. テンプレート list.html を呼び出す.
@GetMapping("/persons")
public String showAllPersons(Model model) {
    List<PersonDto> personList = pm.getAllPersons();
    model.addAttribute("plist", personList);
    return "list";
}

3.2 登録フォームの表示 (GET /persons/register)

登録フォームを表示する

  1. 空のフォームオブジェクトPersonFormを作成する
  2. 画面モデルに,属性PersonFormを定義し,オブジェクトを紐づける
  3. テンプレート register.html を呼び出す.
@GetMapping("/persons/register")
public String showPersonForm(Model model) {
  model.addAttribute("PersonForm", new PersonForm());
    return "register";
}

3.3 受診者情報の登録 (POST /persons)

受診者情報をフォームから受け付けてDBに追加する.その後,一覧に戻る

  1. ページからPOSTされた属性と値のペアを,@ModelAttribute のformに受け取る
  2. @Vaidatedでバリデーションする
  3. エンティティに変換して,登録サービスに渡す addPerson()
  4. /personsにリダイレクトし,一覧ページを表示する
@PostMapping("/persons")
public String addPerson(@ModelAttribute @Validated PersonForm form, BindingResult result, Model model) {
    pm.addPerson(form.toEntity());
    return "redirect:/persons";
}

3.4 更新フォームの表示 (GET /persons/{number}/update)

更新フォームを表示する

  1. パス・パラメータnumberから番号がnumberの受診者pを検索する
  2. 画面モデルに,属性PersonFormを定義し,pを紐づける
  3. テンプレート update.html を呼び出す
@GetMapping("/persons/{number}/update")
public String showUpdateForm(@PathVariable Long number, Model model) {
    PersonDto p = pm.getPerson(number);
    model.addAttribute("PersonForm", p);
    return "update";
}

3.5 受診者情報を更新する (POST /persons/{number}/update)

画面で更新された受診者情報をフォームから受け付けてDBに追加する.~その後,一覧に戻る

  1. ページからPOSTされた属性と値のペアを,@ModelAttribute のformに受け取る
  2. @Vaidatedでバリデーションする
  3. パスパラメータから,受診者番号numberを取得する
  4. formをエンティティに変換して,受診者番号numberとともに更新サービスに渡す updatePerson()
  5. /personsにリダイレクトし,一覧ページを表示する
@PostMapping("/persons/{number}/update")
public String updatePerson(@ModelAttribute @Validated PersonForm form, @PathVariable Long number, Model model) {
    pm.updatePerson(number, form.toEntity());
    return "redirect:/persons";
}

3.6 受診者情報を削除する (GET /persons/{number}/delete)

受診者情報を削除する (確認ルーチンは省略している)

  1. パスパラメータから,受診者情報numberを取得する
  2. 削除サービスを呼び出す deletePerson
  3. /personsにリダイレクトし,一覧ページを表示する
@GetMapping("/persons/{number}/delete")
public String deletePerson(@PathVariable Long number, Model model) {
    pm.deletePerson(number);
    
    return "redirect:/persons";
}

4. HTMLテンプレートを構成する

4.1 扉ページ (static/index.html)

何の変哲もない,普通のHTML.ボタンを押すとpersonsに移動(GET)する

<!DOCTYPE html>
<html lang="ja">

<head>
    <title>健康診断Webアプリケーション</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="UTF-8">
</head>

<body>
    <h1>健康診断Webアプリケーション</h1>
    <div class="title">
        <img src="img/shinchou.png" width="250px">
        <img src="img/taijuu.png" width="250px">
    </div>
    <div>
        <input type="button" class="simple_square_btn5" onclick="location.href='persons'" value="受信者一覧へ" />
    </div>
</body>

</html>

4.2 受診者一覧 (templates/list.html)

Thymeleafのテンプレート.重要なポイントが含まれる

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>受診者一覧</title>
    <link rel="stylesheet" type="text/css" th:href="@{/style.css}">
</head>

<body>
    <h1>受診者一覧</h1>
    <input type="button" class="simple_square_btn5" th:onclick="|location.href='@{/persons/register}'|" value="新規受診者登録" />
    <input type="button" class="simple_square_btn5" th:onclick="|location.href='@{/}'|" value="タイトル画面へ" />
    <table class="tablist">
        <tr>
            <th>No.</th>
            <th>受診者氏名</th>
            <th>生年月日</th>
            <th>身長</th>
            <th>体重</th>
            <th>BMI</th>
            <th>肥満度</th>
            <th>コマンド</th>
        </tr>
        <tr th:each="p: ${plist}">
            <td> [[${p.number}]] </td>
            <td> [[${p.name}]] </td>
            <td> [[${p.birthday}]] </td>
            <td> [[${p.height}]] cm </td>
            <td> [[${p.weight}]] kg </td>
            <td> [[${#numbers.formatDecimal(p.bmi == null ? 0 : p.bmi, 0, 1)}]] </td>

            <th:block th:switch="${p.obesity}">
                <td th:case="'低体重'" style="background-color: #7c7cfc;">[[${p.obesity}]]</td>
                <td th:case="'普通体重'" style="background-color: #7cfc7c;">[[${p.obesity}]]</td>
                <td th:case="'肥満 (1度)'" style="background-color: #fcfc7c;">[[${p.obesity}]]</td>
                <td th:case="'肥満 (2度)'" style="background-color: #fcbb91;">[[${p.obesity}]]</td>
                <td th:case="'肥満 (3度)'" style="background-color: #fc9191;">[[${p.obesity}]]</td>
                <td th:case="'肥満 (4度)'" style="background-color: #c08080;">[[${p.obesity}]]</td>
                <td th:case="*">[[${p.obesity}]]</td>
            </th:block>

            <td>
                <a th:href=@{/persons/{num}/update(num=${p.number})}>編集</a> |
                <a th:href=@{/persons/{num}/delete(num=${p.number})}>削除</a>
            </td>
        </tr>
    </table>
</body>

解説

4.3 受診者登録 (templates/register.html)

登録フォーム.

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>受診者情報登録</title>
    <link rel="stylesheet" type="text/css" th:href="@{/style.css}">
</head>

<body>
    <h1>受診者情報登録</h1>

    <form role="form" th:action="@{/persons}" th:object="${PersonForm}" method="post">
        <table class="tabform large">
            <tr>
                <td><label for="number">No: </label></td>
                <td><span th:text="${number}"></span></td>
            </tr>
            <tr>
                <td><label for="name">氏名: </label></td>
                <td><input class="large" type="text" required th:field="*{name}" /></td>
            </tr>
            <tr>
                <td><label for="birthday">生年月日:</label></td>
                <td><input class="large" type="text" required th:field="*{birthday}" /></td>
            </tr>
            <tr>
                <td><label for="height">身長:</label></td>
                <td><input class="large" type="number" required min="0" value="160" step=".1" th:field="*{height}" /> cm</td>
            </tr>
            <tr>
                <td><label for="weight">体重:</label></td>
                <td><input class="large" type="number" required min="0" value="60" step=".1" th:field="*{weight}" /> kg</td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="button" class="large simple_square_btn5" onclick="history.back()" value="戻る" />
                    <input type="submit" class="large simple_square_btn5" value="登録する" />
                </td>
            </tr>
        </table>
    </form>

</body>

</html>

解説

4.4 受診者更新 (templates/update.html)

4.3とほぼ同じなので割愛

完全なコード

参考文献


トップ   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS