04. SpringBoot + Thymeleafでフロントエンドを作ってみた

# 概要
今回はAPIではなくフロントエンド(**Thymeleaf**)を作ってみようと思います。
「前回まではAPIだったのに突然フロントエンドなんてできるのか？」とお思いの方もいらっしゃるかもしれませんが、
筆者の感覚としては**APIもフロントエンドも実装はそこまで変わりません**。

ある程度のルール(ファイルの配置箇所など)を守ればSpringBootがよしなにやってくれるので、
主に変わるのは**Controller層**くらいで、**それにView層が追加される**くらいなものです。
つまり**Model(Service、Mapper)層はAPIと同じような構成、実装で構わない**ということです。

では実際にソースを見ていきましょう！

# 本題
[前回](https://qiita.com/supreme0110/items/4b7ab92da519c46b8082)までは同じプロジェクトを使用していましたが、
今回はフロントエンドになるので新しいプロジェクトを切っていきます。（手順は割愛します）
作り物としてはシンプルに、前回と同じく郵便番号検索APIにリクエストし、その結果をhtmlにして返してくれるものとしましょう。
今回のメインはThymeleafによるViewの実装なので、そちらの解説をメインにしていきます。

## 1. 依存を追加する
お馴染み[SpringInitializr](https://start.spring.io/)を使っていきます。
今回は「**Web**」「**MySQL**」「**MyBatis**」「**Lombok**」「**Thymeleaf**」を指定して作っていきます。
![依存追加.png](https://qiita-image-store.s3.amazonaws.com/0/188440/72f40aea-a1b0-3494-c2cc-28edfcfdf034.png)

上記を選択したら**GenerateProject**をクリックし、ダウンロードできたzipの中にある**build.gradle**をプロジェクトに反映させます。
**Reflesh**を忘れずに。

## 2. Modelを作る
[前回](https://qiita.com/supreme0110/items/4b7ab92da519c46b8082)作成したGetAddressApiClientとResponseHeaderInterceptorをコピーしてそのまま持ってきます。
あ、データクラスも忘れずに持ってきましょう。

## 3. ControllerとViewを作る
### 3-1. トップ画面を出すControllerとView
とりあえずトップ画面です。
SpringBootでは**リダイレクト**と**フォワード**が利用可能です。今回はリダイレクトを使用しています。

``` TopController.java
@Controller
public class TopController {
    @GetMapping("/top")
    public String top() {
        return "/top";
    }
    @GetMapping("/")
    public String index() {
        return "redirect:/top"; // この記述で/topにリダイレクト可能になります
    }
}
```

つづいてhtmlですが、載せるほどのものではないのでここでは割愛します。
単純なアンカーで検索画面に飛ばすだけです。

### 3-2. 検索と結果を表示するControllerとViewを作る
まとめてバリデーションも実装しちゃいましょう。
まずはControllerから。

``` AddressSearchController.java
@Controller
@RequiredArgsConstructor
public class AddressSearchController {
    private final GetAddressApiClient client;

    @GetMapping("/address/search")
    public String search(@ModelAttribute SearchForm form) { // (1)
        return "/address/search";
    }

    @PostMapping("/address/confirm")
    public String confirm(@ModelAttribute @Valid SearchForm form,
                          BindingResult bindingResult,
                          Model model) {
        if (bindingResult.hasErrors()) { // (2)
            return "/address/search";
        }
        GetAddressApiResponse response = client.request(form.getPostalCode());
        model.addAttribute("addressList", response.getResults()); // (3)
        return "/address/confirm";
    }
}
```
では要点を簡単に説明していきます。
(1). `@ModelAttribute`を引数に付与してModelからformを取り出しています。**初期表示のタイミングではModelにインスタンスが存在しないため、Springが`new`し、そのオブジェクトを渡してくれます**。メソッド内で明示的に`new`して`model.addAttribute()`しても同じことですが、この記述をしておけば`forward`で値を引き継げるので便利です！
(2). お馴染み`bindingResult.hasErrors()`で入力された郵便番号に不正があった場合には検索画面に戻します。因みにアノテーションは前回作った`ZipCode`をそのまま流用しています。
(3). 入力された郵便番号に不正がなければ前回作成した`Client`を介して住所情報を取得し、`addressList`という名前でViewに値を渡します。

続いて検索画面のhtmlです。

``` search.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!-- (1) -->
<head>
    <meta charset="UTF-8">
    <title>Sample Search</title>
</head>
<body>
<h2>検索画面</h2>
<form th:action="@{/address/confirm}" th:method="post" th:object="${searchForm}">
    <label th:for="*{postalCode}">郵便番号</label><input th:field="*{postalCode}">
    <span th:if="${#fields.hasErrors('postalCode')}" th:errors="*{postalCode}">error message</span><br/> <!-- (2) -->
    <button type="submit">検索する</button>
</form>
</body>
</html>
```
まず初めにこのhtmlで登場している各`th`タグや記述方法について説明していきましょう。

- **変数式`${}`**…Controllerから渡されたインスタンスの値を使用する場合に使用する記述方法。**単純に「値を表示したいな」と思ったら使用する**と考えていただければよいかと思います！
- **リンク式`@{}`**…リンクを記述する場合に使用できる記述方法。コンテキストパスを補完してくれます。ここでは紹介しませんが、getパラメタを変数で埋め込んだりもできます。
- **選択変数式`*{}`**…ネストされた変数の値を表示する場合に使用する記述方式。このhtmlでは`th:object="${searchForm}"`で展開されたインスタンスにネストされている`postalCode`にアクセスしている、と考えてください。
- `th:action`…アクション属性を指定します。
- `th:for`…labelの`for`属性を指定します。
- `th:field`…**よく使用するであろうタグです。**この記述をすると、**`id`と`name`属性に変数名が入り、`value`が空の状態でhtmlが作成されます**。formとhtmlのフィールド同士をマッピングするときによく使用します。
- `th:if`…**よく使用するであろうタグです。**値を評価した結果が`true`であればそのタグが描画されます。

(1). thymeleafのタグ`th`を使用するための記述です。これを記載しないとthymeleafを使用できないので忘れないようにしましょう。
(2). `${#fields.hasErrors('postalCode')}`によって、`postalCode`の値でバリデーション違反が発生した場合に`true`を返してくれます。`th:errors`でフィールド名を指定することにより、そのフィールドに対するエラーメッセージを表示できます。つまり**この一文でバリデーション違反があった場合のエラーメッセージ出力ができます**。

最後に確認画面のhtmlです。

``` confirm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Sample Confirm</title>
</head>
<body>
<h2>検索結果画面</h2>
<table border="1">
    <tr>
        <th>郵便番号</th>
        <th>都道府県コード</th>
        <th>住所1</th>
        <th>住所2</th>
        <th>住所3</th>
        <th>住所カナ1</th>
        <th>住所カナ2</th>
        <th>住所カナ3</th>
    </tr>
    <tr th:each="result : ${addressList}">
        <td th:text="${result.zipcode}"></td>
        <td th:text="${result.prefcode}"></td>
        <td th:text="${result.address1}"></td>
        <td th:text="${result.address2}"></td>
        <td th:text="${result.address3}"></td>
        <td th:text="${result.kana1}"></td>
        <td th:text="${result.kana2}"></td>
        <td th:text="${result.kana3}"></td>
    </tr>
</table>
<a th:href="@{/address/search}">検索画面に戻る</a>
</body>
</html>
```
今回は結果がリストで帰ってくるので、テーブルで出力させる形にしました。
この画面で使用しているタグについては以下の通りです。

- `th:each`…**よく使用するであろうタグです。**javaの拡張for文をイメージしていただければわかりやすいかと思います。`${addressList}`から値を順番に取り出し、その値を`result`という名前で使用できるようにしています。
- `th:text`…**よく使用するであろうタグです。**その名の通り、画面表示させたい場合に使用し、レンダリング後は`<td></td>`に値が展開されます。尚XSSに対応しており、メタ文字をエスケープしてくれますので安心して使用できます。

こんなところでしょうか。
では最後にブラウザから叩いてみましょう！！

## 4. 実際に叩いてみる
### 4-1. トップ画面
bootRunして http://localhost:8080/ にアクセスします。
![トップ画面.png](https://qiita-image-store.s3.amazonaws.com/0/188440/380df913-a6c2-3e9d-103f-f01b8f435f2d.png)
**郵便番号で検索する**をクリックして、検索画面へ遷移してみます。

### 4-2. 検索画面
![検索画面.png](https://qiita-image-store.s3.amazonaws.com/0/188440/dc08232b-44da-5226-b7e2-d0bbed885c70.png)

レンダリング後のHTMLソースはこんな感じです。
![検索画面_html.png](https://qiita-image-store.s3.amazonaws.com/0/188440/ca0840a1-76e1-d81d-1878-d324ea5f7363.png)
`th:field`が展開されて**`id`と`name`が変数名になり、`value`が空**になっていますね。
では正常な値を入れて**検索する**をクリックしましょう。

### 4-3. 結果画面
まずは結果が単数の場合から。
![結果画面.png](https://qiita-image-store.s3.amazonaws.com/0/188440/676e895b-e67a-4cfe-9f4e-f97ca99da2a1.png)

続いて結果が複数の場合。
![結果画面_複数.png](https://qiita-image-store.s3.amazonaws.com/0/188440/bc8ba0d5-f744-c63a-ea29-6682c6f650ed.png)
`th:each`がちゃんと機能して、複数の場合でも全件表示できていますね。

では最後にバリデーションエラーの場合を見てみましょう。

### 4-4. バリデーションエラー
![検索画面_不正値.png](https://qiita-image-store.s3.amazonaws.com/0/188440/fc17c684-f8f5-ce6b-a93c-9e3af9eab696.png)
不正な値を入れて**検索する**をクリックすると…

![検索画面_不正値_エラー.png](https://qiita-image-store.s3.amazonaws.com/0/188440/c3b4070b-a1f5-4082-e6ac-80d2e33dd55a.png)
無事エラーメッセージが表示できましたね。
因みにこのメッセージは`ZipCode.message`のデフォルトにべた書きしている文字列です。

# 最後に
めちゃくちゃ簡単な内容ではありましたが、フロントエンドの実装でした。
**thymeleafは奥が深く、私が触れているタグや記述方式は氷山の一角です。**
もっともっといろいろなことができますので、いろいろ調べてみてください！

あとはcssやjavascriptなどが必要になるかと思いますが、最近**`semantic ui`**に触れる機会がありました。
CDNで利用可能で、class名を指定することで勝手に装飾してくれますので、
興味のある方はぜひ触れてみてください！

最後までご覧いただきありがとうございましたー！！