Maciaz
Maciaz

Reputation: 1224

Spring MVC - Thymeleaf form binding with an arraylist

On a specific view I do have an expandable list of employees. Each employee has 2 fields and those are hoursWorked & advancePayments. With 1 button click, I would like to post the whole form. To achieve this, I'm sending a list to the view that contains several POJOs (based on number of employees).

A POJOs that are added to the list look like this:

@Setter
@Getter
@NoArgsConstructor
public class WorkdayCommand {

    private Long employeeId;
    private Integer hoursWorked;
    private Integer advancePayment;
}

In a controller, I do have a line to add the list to the model:

model.addAttribute("workdayCommands", employeeService.getListOfWorkdayCommandsWithIds());

And the method forming the actual list:

public List<WorkdayCommand> getListOfWorkdayCommandsWithIds(){

    List<WorkdayCommand> listOfCommands = new ArrayList<>();
    List<Long> listOfIds = employeeRepository.getListOfIds();

    for(int i = 0; i < employeeRepository.getNumberOfEmployees(); i++){

        WorkdayCommand workdayCommand = new WorkdayCommand();
        workdayCommand.setEmployeeId(listOfIds.get(i));
        listOfCommands.add(workdayCommand);
    }

    return listOfCommands;
}

Now, I am having a problem with my view:

<div class="table-responsive" th:if="${not #lists.isEmpty(employees)}">
    <form th:object="${workdayCommands}" th:action="@{/addworkday}">
         some table headers...
         <tr th:each="employee : ${employees}">
              <td><a href="#" role="button" th:href="@{'/employee/' + ${employee.id}}" th:text="${employee.name}">Mike Kowalsky</a></td>
              <td><input type="text" class="form-control" placeholder="Enter hours" th:field="*{hoursWorked}"></td>
              <td><input type="text" class="form-control" placeholder="Enter payment" th:field="*{advancePayment}"></td>
              <td><input type="hidden" th:field="*{id}"/></td>
         </tr>
    </form>
</div>

So far, I keep getting an error:

NotReadablePropertyException: Invalid property 'hoursWorked' of bean class [java.util.ArrayList]: Bean property 'hoursWorked' is not readable or has an invalid getter method

How to properly bind the arraylist with the view? I guess the problem is that there's no such field in the arraylist as hoursWorked. What th:field parameter should I use, to get to the actual WorkdayCommand.hoursWorked field and then iterate through the list to get through all the employees? If you need any more information, feel free to ask any time.

I'm trying with things like:

th:field="*{[__${iter.index}__].hoursWorked}"

...but that still doesn't work. I can't relate to the first POJO on the list.

edit 2

My full view looks like this: enter image description here

In a single table row I do have some employee information as well as 2 inputs and 2 buttons. Each row is created thanks to:

 <tr th:each="employee : ${employees}">

When a submit button is hit, a new Workday object is created and then persisted to the database. When this happens, a Workday needs to be associated to a corresponding employee. So with my:

 <tr th:each="employee : ${employees}">

...I'm also assigning a hidden id field. Then we have the WorkdayCommand that gathers all the information from the view. So the employeeId field is my way of associating a Workday to a corresponding employee. It should use the id value that was passed by for each that displayed all the information. Hope it's clear now.

Upvotes: 3

Views: 2616

Answers (1)

Metroids
Metroids

Reputation: 20477

You can't bind directly to an ArrayList aS the th:object. Instead you should create an object that has the ArrayList as a property.

@Setter
@Getter
@NoArgsConstructor
public class WorkdayForm {
    private List<WorkdayCommand> workdayCommands = new ArrayList<>();
}

Then you loop through, and bind them like this:

<form th:object="${workdayForm}" th:action="@{/addworkday}">
  <!-- table headers -->
  <tr th:each="workdayCommand, i : ${workdayForm.workdayCommand}">
      <td><input type="text" class="form-control" placeholder="Enter hours" th:field="*{workdayCommands[__${i.index}__].hoursWorked}"></td>
      <td><input type="text" class="form-control" placeholder="Enter payment" th:field="*{workdayCommands[__${i.index}__].advancePayment}"></td>
      <td><input type="hidden" th:field="*{workdayCommands[__${i.index}__].employeeId}"/></td>
  </tr>
  <!-- table footers -->
</form>

Upvotes: 5

Related Questions