Thymeleaf and lists in forms

Posted by Tobias on April 8, 2013 00:00 CEST Updated on April 10, 2013 22:18 CEST 11 Comments Add a comment

Contents

Thymeleaf is great. Previously I have worked a lot with JSP and Facelets and even Razor which is the one view engine in Microsoft MVC. My favourite so far is Thymeleaf even though I really enjoyed working with Razor. Recently I needed to create a view to edit multiple instances of a model directly. So I created a view which rendered a form and a table based on a model which consisted of a List. Now I needed to make Thymeleaf understand that my th:field actually was a property in an model instance in the list. This wasn't very easy. However, a forum reply by Daniel Fernandez who is the creator of Thymeleaf lead me on the right path.

According to Mr. Fernandez I had to use a somewhat funny looking syntax which would look something like this:

<div th:each="userAddress, stat : *{userAddresses}">
...
<input type="text" th:field="*{userAddresses[__${stat.index}__].userStreet}" />
...
</div>

I assumed that userAddresses is a list of something so I tried to do the same thing but with a list of PersonViewModel:

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class PersonViewModel {
	
	@Size(min=1,max=255)
	@NotNull
	private String name;

	@Size(min=1,max=255)
	@NotNull
	private String address;

	// Getters and setters omitted
}

I could however not get this to work. Instead I received an error message stating

Field or property 'model' cannot be found on object of type 'java.util.ArrayList'

The solution I eventually came up with was this:

The view:

<form action="#" th:object="${model}" th:action="@{/people/edit/}" method="post">
<ul id="validation-messages" th:if="${#fields.hasErrors('*')}">
<li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>
<table>
<caption>People</caption>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Address</th>
<th></th>
</tr>
</thead>

<tbody>
<tr th:each="person, rowStat : *{personList}" th:class="${rowStat.even ? 'even' : 'odd'}">
<td><input type="text" th:field="*{personList[__${rowStat.index}__].id}" readonly="readonly" /></td>
<td><input type="text" th:field="*{personList[__${rowStat.index}__].name}" /></td>
<td><input type="text" th:field="*{personList[__${rowStat.index}__].address}" /></td>
<td><a th:href="@{/people/delete/} + *{personList[__${rowStat.index}__].id} + '/'">Delete</a></td>
</tr>
</tbody>

<tfoot></tfoot>
</table>
<input type="submit" value="Save" />
</form>

List container model:

import java.util.ArrayList;
import java.util.List;

import javax.validation.Valid;

public class PersonListFormViewModel {

@Valid
private List<PersonViewModel> personList = new ArrayList<PersonViewModel>();

public PersonListFormViewModel() {}

public PersonListFormViewModel(List<PersonViewModel> personList) {
this.personList = personList;
}

public List getPersonList() {
return personList;
}

public void setPersonList(List<PersonViewModel> personList) {
this.personList = personList;
}
}

Basically I created a wrapper class for the List and supplied the view with that instead of the list directly.

One thing to note about having a view which enables edits of more than one entity at once, if you have service methods which looks something like this:

public void save(PersonViewModel person) {
	person.setUpdatedBy(session.getCurrentUser());
	personDao.save(person);
}

public void save(PersonFormViewModel personList) {
	for (PersonViewModel person : personList) {
		save(person);
	}
}

Now, if a user edits one of the instances of PersonViewModel using the multi edit view it will look like the user updated all of the instances.

Post your comment

11 Comments (newest first)

Posted by Enter your name on July 6, 2017 10:44 CEST

Enter your comment

Posted by xv on July 4, 2017 05:13 CEST

v

Posted by ad on June 9, 2017 16:45 CEST

asd

Posted by hi on March 8, 2017 10:46 CET

hi

Posted by zammad on February 24, 2017 13:45 CET

test

Posted by Edgar Arakelyan on August 4, 2016 23:24 CEST

Great! You saved me after 2 hours of vain google search. Finally got rid of that stupid problem :)

Posted by hghg454354 on April 15, 2016 10:20 CEST

dgdfgfd

Posted by ddd on March 4, 2016 13:33 CET

ddd

Posted by krystofurr on November 17, 2015 02:47 CET

I would also like to see the controller and how it's handled when it returns to the request mapping. Great posting btw!

Posted by Andy Brave on August 26, 2015 09:30 CEST

Hi, I have the same problem but I can't understand it. Can you show me your controller? or How do you send the equest? Thanks!

Posted by fg on February 11, 2014 17:29 CET

fgfg

Read More