I am trying to pass a List of "Experience" objects which size is unknown beforehand. The user can add as many experiences as he wants on the FE, so I don't know the number of objects in advance.
This is part of the Controller:
@PostMapping("/buildcv")
public ResponseEntity<byte[]> buildcv(@ModelAttribute("experience") ExperienceDTO experience{
...
}
The DTO:
public class ExperienceDTO {
private List<Experience> experienceList;
//getters and setters
}
The Experience Entity:
@Entity
public class Experience {
@Id
@GeneratedValue
private int id;
private String company;
private String position;
private String responsibilities;
private Date startDate;
private Date endDate;
//getters and setters
The View:
<div class="work-expirience-fields" id="work-expirience">
<a class="remove-image" id="close-button">×</a>
<div class="col-one-row-one">
<label>Job title</label>
<input th:field="${experience.position}" id="jobTitle">
</div>
<div class="col-two-row-one">
<label>Employer</label>
<input th:field="${experience.company}" id="employer">
</div>
....
</div>
</div>
This examples works if there is only one single Experience object passed to the controller instead of the DTO with the list in it. However there is button which copies this most outer div with all of the tags in it.
I tried using spring:bind with setting a path for each field like this but the list was not populated to the controller and I couldn't find an example which quite describes what I want to do:
<spring:bind path="experience.experienceList[0]">
<a class="remove-image" id="close-button">×</a>
<div class="col-one-row-one">
<label>Job title</label>
<input th:field="${experience.position}" id="jobTitle">
</div>
.......
The input names must be indexed, for them to bind to List
of objects in the model object (that is ExperienceDTO
).
One important point is, it is not good practice to use @Entity
objects as the View objects. You should have a separate POJO
and map it from Entity
. But let us ignore this point for this discussion.
Your thymeleaf template should look like this,
<form th:action="@{/buildcv}" th:object="${experience}" method="post">
<div th:each="experienceItem, expState : *{experienceList}">
<div>
<label>Job Title</label>
<input th:field="*{experienceList[__${expState.index}__].position}"/>
</div>
<div>
<label>Employer</label>
<input th:field="*{experienceList[__${expState.index}__].company}"/>
</div>
......
</div>
</form>
That results in, the first div
on the HTML page as,
<div>
<div>
<label>Job Title</label>
<input id="experienceList0.position" name="experienceList[0].position" value="">
</div>
<div>
<label>Employer</label>
<input id="experienceList0.company" name="experienceList[0].company" value="">
</div>
...
</div>
and the second row will be
<div>
<div>
<label>Job Title</label>
<input id="experienceList1.position" name="experienceList[1].position" value="">
</div>
<div>
<label>Employer</label>
<input id="experienceList1.company" name="experienceList[1].company" value="">
</div>
...
</div>
As you can see the name
attribute values are index accordingly,
experienceList[0].position
, experienceList[0].company
and
experienceList[1].position
, experienceList[1].company
The th:object="expereince"
in the form
element is the model attribute name that refers to ExperienceDTO
object.
Ideally (but not necessary, it depends on how you get to the form page) there should be a separate GetMapping
controller method, that loads the form, there the model object is set before redirecting to view.
.....
model.addAttribute("experience", experienceDTO);
....
return "buildCvForm";
Assuming buildCvForm.html
is your thymeleaf template name.
You mentioned that the rows can be added on the page in Front-End, I assume it might be by using some Front-End framework like JQuery, Angular or something else.
Irrespective of what is used in Front-End, when new rows are added, they have to adhere to the indexing of name
s.
Let us say a user already has 2 Experience
s that the page is loaded with and on Front-End when a 3rd row is added, then the input name
s must be
experienceList[2].position
, experienceList[2].company
As long as the name
s are correctly indexed for every set of Experience
fields, the form data is sent correctly for your controller to bind them into the List
inside the ExperienceDTO
model attribute.
Refer to Thymeleaf documentation here