Spring+Thymeleafで動的フォーム
Thymeleafの機能で動的フォームを実装する方法について。
Thymeleafの公式チュートリアルにも記載されている、サーバ側でリスト操作して画面に再表示する方法をやってみました。
環境
- Spring Boot: 2.3.3
- Thymeleaf: 3.0.11
実装例
単語帳を例に以下のような画面を作成します。
「追加」ボタンを押下すると、リストの一番下に入力欄が追加されます。
入力欄の右側にある「削除」ボタンを押下すると、その行の入力欄が削除されます。
実装方法
フォームのドメインクラス(DTO)
まずドメインクラスを以下のように作成します。
@Data
public class Flashcard {
private String title; // 単語帳のタイトル
private List<Card> cardList; // 単語リスト
}
@Data
public class Card {
private String word; // 単語
private String detail; // 説明
}
Thymeleaf
<form method="post" th:action="@{/flashcard/edit}" th:object="${flashcard}">
<label>タイトル</label>
<input type="text" th:field="*{title}"><br>
<table>
<tr>
<th>単語</th>
<th>説明</th>
<th></th>
</tr>
<tr th:each="card, cardStat : *{cardList}"> <!-- ① -->
<td>
<!-- ② -->
<input type="text" th:field="*{cardList[__${cardStat.index}__].word}">
</td>
<td>
<!-- ② -->
<input type="text" th:field="*{cardList[__${cardStat.index}__].detail}">
</td>
<td>
<!-- ③ -->
<button type="submit" name="remove" th:value="${cardStat.index}">削除</button>
</td>
</tr>
</table>
<button type="submit" name="add">追加</button>
</form>
要点は以下の通りです。
①繰り返し処理
th:each="card, cardStat : *{cardList}"
card
:仮変数cardStat
:繰り返し処理のステータス変数
②データバインド
th:field="*{cardList[__${cardStat.index}__].word}"
- 繰り返し処理以外の時と同様に
th:field
属性でデータバインディングします。 - データバインディングには仮変数は使わず、リストのインデックスを指定した形式で記述します。
- リストのインデックスにはプリプロセッシング式(
__${expression}__
)を使用します。
③ボタンに値を設定する
th:value="${cardStat.index}"
th:value
属性でサーバに送信するボタンの値を設定します。- ボタンの値には、ステータス変数のindexプロパティで行番号を指定します。
Spring:コントローラクラス
@Controller
public class FlashcardController {
// ①初期表示
@GetMapping("/flashcard")
public String load(@ModelAttribute Flashcard flashcard, Model model) {
// リストの初期化(入力欄を常に1行以上表示する)
List<Card> list = new ArrayList<Card>();
Card card = new Card();
list.add(card);
flashcard.setCardList(list);
return "flashcard";
}
// ②「追加」ボタン押下
@PostMapping(value = "/flashcard/edit", params = "add")
public String addList(@ModelAttribute Flashcard flashcard, Model model) {
// リスト最後尾に1行追加
flashcard.addList();
return "flashcard";
}
// ③「削除」ボタン押下
@PostMapping(value = "/flashcard/edit", params = "remove")
public String removeList(@ModelAttribute Flashcard flashcard, Model model, HttpServletRequest request) {
// 行番号を指定して削除
int index = Integer.valueOf(request.getParameter("remove"));
flashcard.removeList(index);
return "flashcard";
}
}
要点は以下の通りです。
①初期表示
- リストを初期化する時、最低1行は表示するようにします。
- リストの要素数が0の場合、HTMLに変換される時に
<tr>
の中身が空になり、「追加」ボタンを押下した時にエラーになります。
②「追加」ボタン押下
- ドメインクラスに作成したメソッドを呼び出して、リストに要素を一つ追加します。
③「削除」ボタン押下
- HttpServletRequestオブジェクトからサーバに送信された値を取得できます。
request.getParameter("remove")
で削除ボタンに設定した名前を指定して、value値を取り出します。- ドメインクラスに作成したメソッドを呼び出して、リストから指定した位置の要素を削除します。
Spring:ドメインクラス
リストへの追加・削除のために、以下のようにメソッドを追加します。
@Data
public class Flashcard {
private String title; // 単語帳のタイトル
private List<Card> cardList; // 単語リスト
// リストに新規項目を追加
public void addList() {
Card card = new Card();
cardList.add(card);
}
// リストから項目を削除
public void removeList(int index) {
// 入力欄は最低1行は残す
if (cardList.size() > 1) {
cardList.remove(index);
}
}
}
削除メソッド
- リストの要素数が1以下の場合は要素を削除しないようにします。
- 初期表示時と同様に、リストの要素数が0の場合「追加」ボタン押下時にエラーとなります。