«1. Обзор

В этом кратком руководстве мы покажем, как связать объект List в Thymeleaf.

Чтобы узнать, как интегрировать Thymeleaf с Spring, вы можете ознакомиться с нашей основной статьей Spring здесь, где вы также можете узнать, как отображать поля, принимать ввод, отображать ошибки проверки или преобразовывать данные для отображения.

2. Списки в примере Thymeleaf

Мы начнем с демонстрации того, как отображать элементы списка на странице Thymeleaf и как связать список объектов в качестве входных данных пользователя в форме Thymeleaf.

Для этой цели мы будем использовать простую модель, показанную в следующем коде:

public class Book {
    private long id;

    private String title;

    private String author;
	
    // getters and setters
}

Помимо отображения существующих книг в нашем примере, мы дадим пользователю возможность добавлять несколько книг в коллекцию, а также редактировать все существующие книги одновременно.

3. Отображение элементов списка

Давайте взглянем на следующий метод Controller, который возвращает страницу allBooks:

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

Здесь мы добавили объекты List of Book в качестве атрибута модели, отправляемого в представление. , где мы отобразим его с помощью таблицы 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
}

Теперь давайте позволим пользователю добавить три книги в одну отправку формы.

Сначала мы подготовим страницу формы, передав наш объект команды в качестве атрибута модели:

@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}â€, чтобы указать объект команды (тот, который мы передали как атрибут модели).

Следующее, что стоит отметить, это то, что мы получили доступ к списку с помощью выражения выбора, используя:

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

И, наконец, мы отображаем наши входные данные как свойства элементов списка, используя th:field.

Однако нам также нужно использовать переменную itemStat, чтобы определить, к какому элементу списка мы обращаемся, как показано в:

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

Последний шаг — фактически манипулировать отправленными данными на серверной части. Мы будем использовать объект команды в качестве @ModelAttribute в нашем методе @PostMapping в контроллере, сохраним полученный список книг и вернем пользователю все существующие книги:

@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>

Как показано в \u003ctr th:each=†book, itemStat : ${form.books}†\u003e, мы получили доступ к списку немного другим способом, на этот раз используя переменное выражение. Особенно уместно отметить, что мы указали имя и значение для элементов ввода для правильной отправки данных.

Нам также пришлось добавить скрытый ввод, который будет связывать идентификатор текущей книги, потому что мы хотим не создавать новые книги, а редактировать существующие.

6. Заключение

В этой статье мы показали, как использовать объект List в Thymeleaf и Spring MVC. Мы показали, как отобразить список объектов, отправленных в представление, но сосредоточили основное внимание на двух способах привязки пользовательского ввода в виде списка в форме Thymeleaf.

Все фрагменты кода, упомянутые в статье, можно найти в нашем репозитории GitHub.