简体   繁体   中英

Thymeleaf dynamically create forms using th:each

I would like to know how to create forms that uses th:object for each object looped in a th:each . For example, I have the following code.

HTML

<th:block th:each="store: ${stores}">
    <form th:object="${store}" th:action="@{/modify-store}">
        <input th:field="*{idStorePk}"/>
        <input th:field="*{name}"/>
        <input th:field="*{phoneNumber}"/>
        <button type="submit">Modify</button>
    </form>
</th:block>

Controller

@RequestMapping(value = "/stores")
public String getIndex(Model model) {
    model.addAttribute("stores", storeService.getAllStores());
    return "store";
}

So, I would like to add a form for each object, but it seems that it is not possible and I get the following error.

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'store' available as request attribute

So, I decided to add a @ModelAttribute in my controller, but can't get to return the actual store.

@ModelAttribute("store")
public Store getStore(Store store) {
    return store;
}

With this approach all my forms have null values. I also tried to add a @PathVariable , but can't see to bind it using th:object . Is there a solution for this?

So for anyone stuck at a similar problem. I find out a work around that might help you out. First, you can't use th:object , it simply won't cut it. Instead, do the following.

<th:block th:each="store: ${stores}">
    <form class="store-form" th:action="@{/modify-store}">
        <input th:name="idStorePk" th:value="${store.idStorePk}"/>
        <input th:name="name" th:value="${store.name}"/>
        <input th:name="phoneNumber" th:value="${store.phoneNumber}"/>
        <button class="submit-button" type="submit">Modify</button>
    </form>
</th:block>

Then just add something similar to the controller.

@PostMapping(value = "/modify-store")
@ResponseBody
public boolean deleteEntry(@ModelAttribute Store store) throws Exception {
    // Your code here...
    return true;
}

If you want to send it asynchronously then you will need to add some JS code in order for it to work. It should look something like the code below.

const forms = document.querySelectorAll('.store-form');
forms.forEach(form => {
   form.addEventListener('submit', event => {

   // Stop the normal form submit triggered by the submit button
   event.preventDefault();

   const formInputs = form.getElementsByTagName("input");
   let formData = new FormData();
   for (let input of formInputs) {
       formData.append(input.name, input.value);
   }

   fetch(form.action,
   {
        method: form.method,
        body: formData
   })
   .then(response => response.json())
   .then(data => console.log(data))
   .catch(error => console.log(error.message))
   .finally(() => console.log("Done"));
});

You're sending stores in your controller in model-attribute and on your second controller where you're submitting your form you're using store that's the reason you're getting this error. So correct the spelling error on any one of your controller. Like this :-

@RequestMapping(value = "/stores")
public String getIndex(Model model) {
    model.addAttribute("stores", storeService.getAllStores());
    return "store";
}

And Your second controller where you're submitting your form will be like this -

@ModelAttribute("stores")
public Store getStore(Store store) {
    return store;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM