1. 概要

このクイックチュートリアルでは、ThymeleafでListオブジェクトをバインドする方法を示します。

ThymeleafをSpringと統合する方法については、Springのメイン記事をご覧ください。ここでは、フィールドの表示、入力の受け入れ、検証エラーの表示、または表示用のデータの変換方法も学ぶことができます。

2. Thymeleafの例のリスト

まず、 Thymeleafページにリストの要素を表示する方法と、Thymeleafフォームでユーザーの入力としてオブジェクトのリストをバインドする方法を示します。

この目的のために、次のコードに示す単純なモデルを使用します。

public class Book {
    private long id;

    private String title;

    private String author;
	
    // getters and setters
}

この例では既存の本を表示するだけでなく、ユーザーがコレクションに複数の本を追加したり、既存のすべての本を一度に編集したりできるようにします。

3. リスト要素の表示

allBooksページを返す次のControllerメソッドを見てみましょう。

@GetMapping("/all")
public String showAll(Model model) {
    model.addAttribute("books", bookService.findAll());
    return "books/allBooks";
}

ここでは、ビューに送信されるモデル属性としてBookオブジェクトのListを追加しました。ここでは、HTMLテーブルを使用して表示します。

<table>
    <thead>
        <tr>
            <th> Title </th>
            <th> Author </th>
        </tr>
    </thead>
    <tbody>
	<tr th:if="${books.empty}">
            <td colspan="2"> No Books Available </td>
        </tr>
        <tr th:each="book : ${books}">
            <td><span th:text="${book.title}"> Title </span></td>
            <td><span th:text="${book.author}"> Author </span></td>
        </tr>
    </tbody>
</table>

ここでは、 th:eachプロパティを使用して、リストを反復処理し、その中の各オブジェクトのプロパティを表示しています。

4. 選択式を使用したリストのバインド

フォーム送信を介してビューからコントローラーにオブジェクトのリストを送信するために、Listオブジェクト自体を使用することはできません。

代わりに、送信されたリストを保持するラッパーオブジェクトを追加する必要があります:

public class BooksCreationDto {
    private List<Book> books;

    // default and parameterized constructor

    public void addBook(Book book) {
        this.books.add(book);
    }
	
    // getter and setter
}

ここで、ユーザーが1つのフォーム送信で3冊の本を追加できるようにしましょう。

まず、フォームページを準備し、コマンドオブジェクトをModel属性として渡します。

@GetMapping("/create")
public String showCreateForm(Model model) {
    BooksCreationDto booksForm = new BooksCreationDto();

    for (int i = 1; i <= 3; i++) {
        booksForm.addBook(new Book());
    }

    model.addAttribute("form", booksForm);
    return "books/createBooksForm";
}

ご覧のとおり、3つの空のBookオブジェクトのリストをラッパークラスを介してビューに渡しました。

次に、フォームをThymeleafページに追加する必要があります。

<form action="#" th:action="@{/books/save}" th:object="${form}"
  method="post">
    <fieldset>
        <input type="submit" id="submitButton" th:value="Save">
        <input type="reset" id="resetButton" name="reset" th:value="Reset"/>
        <table>
            <thead>
                <tr>
                    <th> Title</th>
                    <th> Author</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="book, itemStat : *{books}">
                    <td><input th:field="*{books[__${itemStat.index}__].title}" /></td>
                    <td><input th:field="*{books[__${itemStat.index}__].author}" /></td>
                </tr>
            </tbody>
        </table>
    </fieldset>
</form>

そして、これは上のページがどのように見えるかです:

ここで行ったことを詳しく見てみましょう。 まず、 th:object =” $ {form}”を使用して、コマンドobject Model 属性として渡したもの)を指定しました。

次に注目に値するのは、以下を使用して選択式でリストにアクセスしたことです。

<tr th:each="book, itemStat : *{books}">

そして最後に、 th:fieldを使用してリスト要素のプロパティとして入力をマッピングしています。

ただし、次のように、 itemStat 変数を使用して、参照しているリスト要素を定義する必要もあります。

th:field="*{books[__${itemStat.index}__].title}"

最後のステップは、実際にはバックエンドで送信されたデータを操作することです。 コントローラの@PostMappingメソッドで@ModelAttributeとしてコマンドオブジェクトを使用し、取得した書籍のリストを保存して、既存のすべての書籍をユーザーに返します。

@PostMapping("/save")
public String saveBooks(@ModelAttribute BooksCreationDto form, Model model) {
    bookService.saveAll(form.getBooks());

    model.addAttribute("books", bookService.findAll());
    return "redirect:/books/all";
}

フォームを/save エンドポイントに送信すると、新しく追加されたすべての書籍が記載されたページが表示されます。

5. 変数式を使用したリストのバインド

この例では、最初に既存のすべての本をコマンドオブジェクトにロードします。

@GetMapping("/edit")
public String showEditForm(Model model) {
    List<Book> books = new ArrayList<>();
    bookService.findAll().iterator().forEachRemaining(books::add);

    model.addAttribute("form", new BooksCreationDto(books));
    return "books/editBooksForm";
}

HTMLページも同様ですが、 th:eachブロックで最も注目すべき違いがあります。

<tr th:each="book, itemStat : ${form.books}">
    <td>
        <input hidden th:name="|books[${itemStat.index}].id|" th:value="${book.getId()}"/>
    </td>
    <td>
        <input th:name="|books[${itemStat.index}].title|" th:value="${book.getTitle()}"/>
    </td>
    <td>
        <input th:name="|books[${itemStat.index}].author|" th:value="${book.getAuthor()}"/>
    </td>
</tr>

に示すように

、今回は変数式を使用して、少し異なる方法でリストにアクセスしました。 特に関連するのは、データを適切に送信するために入力要素の名前と値を提供したことに注意してください

また、新しい本を作成するのではなく、既存の本を編集するために、現在の本のIDをバインドする非表示の入力を追加する必要がありました。

6. 結論

この記事では、ThymeleafおよびSpringMVCでListオブジェクトを使用する方法を説明しました。 ビューに送信されたオブジェクトのリストを表示する方法を示しましたが、ユーザー入力をThymeleaf形式のリストとしてバインドする2つの方法に主に焦点を当てています。

この記事に記載されているすべてのコードスニペットは、GitHubリポジトリにあります。