Search code examples
javaspringspring-bootthymeleaf

Thymeleaf th:field pre-processing is not working with th:each


I want to display a list of objects in a table along with an option to update one of the field marketallcoation live. And I use thymeleaf for the purpose. So, I had to use th:each in combination with pre-processing capacity available in th:field.

In my controller class, I set the attribute as shown below:

model.addAttribute("marketList",supplyAllocationService.getItems());

And in my html page, I do something like this:

<table>
<tr th:each="market,iteration : *{marketList}">
                <td><span th:text="${market.date}" th:field="*{marketList[__${iteration.index}__].date}"> Date </span></td>
                <td><span th:text="${market.country}" th:field="*{marketList[__${iteration.index}__].country}"> Country </span></td>
                <td><span th:text="${market.product}" th:field="*{marketList[__${iteration.index}__].product}"> Product </span></td>
                <td><span th:text="${market.sku != null} ? ${market.sku} : 'None'" th:field="*{marketList[__${iteration.index}__].sku}"> SKU </span></td>
                <td><span th:text="${market.aggregateddemand}" th:field="*{marketList[__${iteration.index}__].aggregateddemand}"> Aggregated Demand </span></td>
                <td><span th:text="${market.aggsupply_12}" th:field="*{marketList[__${iteration.index}__].aggsupply_12}"> 12 weeks aggregated supply </span></td>
                <td><input type="text" th:value="${market.marketallcoation}" th:field="*{marketList[__${iteration.index}__].marketallcoation}"/></td>
                <td><span th:text="${market.unmetdemand}"  th:field="*{marketList[__${iteration.index}__].unmetdemand}"> Unmet demand quantity </span></td>
                <td><span th:text="${market.demandplanner}" th:field="*{marketList[__${iteration.index}__].demandplanner}"> Demand Planner </span></td>
                <td><span th:text="${market.bdmcontact}" th:field="*{marketList[__${iteration.index}__].bdmcontact}"> BDM contact </span></td>
                <td></td>
            </tr>
</table>

When I run the code, I get the below error:

Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'marketList[0]' available as request attribute
    at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.springframework.web.servlet.support.RequestContext.getBindStatus(RequestContext.java:903) ~[spring-webmvc-5.1.6.RELEASE.jar:5.1.6.RELEASE]
    at org.thymeleaf.spring5.context.webmvc.SpringWebMvcThymeleafRequestContext.getBindStatus(SpringWebMvcThymeleafRequestContext.java:227) ~[thymeleaf-spring5-3.0.11.RELEASE.jar:3.0.11.RELEASE]
    at org.thymeleaf.spring5.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:306) ~[thymeleaf-spring5-3.0.11.RELEASE.jar:3.0.11.RELEASE]

As per the docs, no need of th:object if pre-processing functionality is used. I haven't used it, so I'm not sure what am I missing here.


Solution

    1. If you're using *{...} syntax and/or the th:field attribute, then you have to use a th:object (they both depend on the th:object to function).
    2. You shouldn't be using th:field on a <span/> element -- it doesn't make sense. (th:field sets the name, id, and value attributes of an element. name and value don't affect <span />s.)

    Also, unfortunately I don't think that you can use a List as a form object. So to fix your form, you need to first create a new object that has marketList as one of it's properties, then add that to the model instead. Make that new object the th:object of your form, then the preprocessing should work for you.

    <form th:object="${yourNewObject}>
      <table>
        <tr th:each="market, iteration: *{marketList}">
          <td th:text="${market.date}" />
          <td th:text="${market.country}" />
          <td th:text="${market.product}" />
          <td th:text="${market.sku != null} ? ${market.sku} : 'None'" />
          <td th:text="${market.aggregateddemand}" />
          <td th:text="${market.aggsupply_12}" />
          <td><input type="text" th:field="*{marketList[__${iteration.index}__].marketallcoation}"/></td>
          <td th:text="${market.unmetdemand}" />
          <td th:text="${market.demandplanner}" />
          <td th:text="${market.bdmcontact}" />
          <td></td>
        </tr>
      </table>
    </form>