2

I am having a hardtime to bind a ObjectList in Thymeleaf POST.

I am trying to achieve the below requirements using Spring 5 and ThymeLeaf

1. User uploads a Excel File
2. Display data in HTML Table (For Java Script Data Validation)
3. Allow User to Delete Invalid Rows  (from Inmemory User Specific ArrayList)
4. Display Remaining Values
5. POST valid remaining values to Server

I am planning to add a delete button in each row, . And a Submit button to save all the remaining rows in DB.

How can I forward eachRowList to another controller (for delete action and DB save).

@PostMapping("/load-excel")
public ModelAndView loadFile(@RequestParam("fileName") MultipartFile file,
         @RequestParam("selectedApplicationName") String selectedApplicationName,RedirectAttributes redirectAttr,Model model) {

        List<EachRowinExcel> eachRowList = fileReaderService.convertExceltoObjList();
        ....

        modelAndView.addObject("eachRowList", eachRowList);

        return modelAndView;
}



 <tr th:each="eachRow : ${eachRowList}">
              <td th:text="${eachRow.column1}"></td>
              <td th:text="${eachRow.column2}"></td>
              <td th:text="${eachRow.column3}"></td>
              <td th:text="${eachRow.column4}"></td>
              <td th:text="${eachRow.column5}"></td>
              <td th:text="${eachRow.column6}"></td>
              <td th:text="${eachRow.column7}"></td>
              <!-- Special Columns -->
              <th:block th:each="customColumnValue:${eachRow.customColumnsList}">
                <td th:text="${customColumnValue}"></td>
              </th:block> 

            </tr>

Update 1:

Modified View

<form action="#" th:action="@{/access/delete}" th:object="${form}" method="post">
    <table id="accessRequestDataTable"  class="display compact" style="width:100%">
      <thead>
        <tr>
          <!-- Headers -->       
        </tr>
      </thead>
      <tbody>
     <tr th:each="eachRow, iter : ${form.eachRowList}">
          <td  th:text="${eachRow.accessReqeustCounter}" th:field="*{eachRowList[__${iter.index}__].accessReqeustCounter}"></td>
          <td  th:text="${eachRow.accessReqeustID}" th:field="*{eachRowList[__${iter.index}__].accessReqeustID}"></td>
          <td  th:text="${eachRow.accessRequestType}" th:field="*{eachRowList[__${iter.index}__].accessRequestType}"></td>
          <td  th:text="${eachRow.userProfile}" th:field="*{eachRowList[__${iter.index}__].userProfile}"></td>
          <td  th:text="${eachRow.userFinalName}" th:field="*{eachRowList[__${iter.index}__].userFinalName}"></td>
          <td  th:text="${eachRow.userLoginName}" th:field="*{eachRowList[__${iter.index}__].userLoginName}"></td>
          <td  th:text="${eachRow.userEmail}" th:field="*{eachRowList[__${iter.index}__].userEmail}"></td>

          <td>              
            <button class="btn btn-danger btn-sm" 
            type="submit" value="submit">Delete</button>             
          </td>
        </tr>        

      </tbody>
    </table>
  </form>

POST Controller

@RequestMapping(value = "/access/delete", method = RequestMethod.POST)
public ModelAndView deleteUserFromTable(@ModelAttribute("form") EachRowListWrapper eachRowListWrapper){
    System.out.println(eachRowListWrapper.getEachRowList().size());
    ModelAndView modelAndView = new ModelAndView();
    eachRowListWrapper.getEachRowList().remove(0);
    modelAndView.setViewName("access-table");
    return modelAndView;        
}

Update 2

Followed the similar approach for column headers. I have another List object in the wrapper class. It works at initial load, but headers are missing after returning from a POST controller.

  <thead>
    <tr>              
      <th scope="col">User Name</th>
      <th scope="col">Login</th>
      <th scope="col">Email</th>
      <th:block th:each="key, iter : ${form.customColumns}">
        <th th:text="${key}" scope="col"></th>
        <input type="hidden" th:field="${form.customColumns[__${iter.index}__]}" />
      </th:block> 
      <th scope="col">Action</th>
    </tr>
  </thead>

Final Update:

Apparently th:field input tag will not bind within thead section (its not supposed to have input fields LoL) . Everything works as expected after I moved it before the table starts.

2 Answers 2

4

You need to put your table inside a form, which will determine what will be send to the controller in it's th:object field. This is called a command object in spring. Lets assume for this example that your "eachRowList" list is inside a command object called "form".

<form method="post"
        th:action="@{/mappingInYourController}"
        th:object="${form}">
<!-- Here goes your table -->
</form>

You also have to specify which data will be placed inside the object you defined in th:object, so it can be send to the controller. This is done done with input th:fields in the following way (an example with one of your rows):

<tr th:each="eachRow, iter : ${form.eachRowList}">
<!-- ... -->
<td> <input th:value="${eachRow.column1}" th:field="*{eachRowList[__${iter.index}__].column1}"/>
</td>

Note that when you access attributes with *{}, you are referencing attributes inside what you defined to be the th:object for the form.

Now if you submit the form with a button for example, your controller will be able to recieve the command object "form" with the eachRowList list inside it, and the elements of the list will be those on the inputs of the table. If you do not wish for this inputs to be editable inline in the table, you can do so so with:

th:readonly="true"
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for your time on this Jorge. I have tried your inputs but I get null values in POST method. I have added the modified code.
My bad, those th:field tags must be inside input elements. I'll update the answer, hope it works fine now. Also for the delete buttons on each row, most likely you will want to use buttons formaction attribute to change the action of each button, so you can tell your controller which entry are you selecting for deletion. You can use the [${iter.index}] part on the actions too for this.
Wow !! Thanks a lot. Working perfectly . Almost completed , except for the table headers. I have added update 3 on that. Am I missing something ?
You are using "th:field="${form...". First, for th:fields you should use *{}, not ${} syntax. Second, this is because *{} refers to the th:object of the form. Since this th:object is called "form" in your example, you don't need to reffer to it again using *{}, only when using ${}. The working syntax would be th:field="*{customColumns[__${iter.index}__]}".
I did try this before as you mentioned , then changed it to explicit reference. Anyways both didn't work. Can "form" wrapper class contains mutiple list as members ?
|
1

For your submit button you need a form that surrounds your table. A solution that might work is to have hidden input fields that hold the data for each td.

<input type="hidden" th:field="${eachRow.column1}"/>

After you submit the form, you can access the data held by the input fields in your controller and persist it in your database.

1 Comment

Thanks for the input Happy . I have used hidden attribute.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.