简体   繁体   中英

thymeleaf posting current object from th:each

This is my first time with thymeleaf and I got across a strange problem that I can't seem to get around. A minimal sample:

Consider the following Controller mapping to list all users from a database:

@GetMapping("/users")
public String listUsers(Model model) {
    model.addAttribute("users", userRepository.findAll());
    return "users";
}

Inside the users view, I want to list the users and for each of them I want to add a button to get the details for that particular user:

<table>
  <thead>
    <tr><th>Name</th></tr>
  </thead>
  <tbody>
    <form th:each="user : ${users} th:action="@{/user}" th:object="${user}" method="post">
      <tr>
        <td th:text="${user.getUserName()}">Name</td>
        <td><input type="submit" value="Details" /></td>
      </tr>
    </form>
  </tbody>
</table>

So, since users is a List, my original idea was to create a form for each user in that list, and bind the current ${user} to th:object. Then in the controller mapping for the details (since this might have been changed in the meantime):

@PostMapping("/user")
public String userDetails(@ModelAttribute User user, Model model) {
  model.addAttribute("user", userService.findUserById(user.getId()));
  return "userDetails";
}

Now my problem is that it doesn't work. The ModelAttribute is null in the controller. I've been reading thymeleaf's documentation on forms but can't figure out why I can't post the user from the current iteration without adding all of the user attributes in hidden fields to the form. I might by trying to do something completely insane, but I don't know honestly... Any tips for me? :)

@Update: After countless rewrites I settled with the following:

@GetMapping("/users/{id}/details")
public String user(@RequestParam Integer userId, Model model) {
    model.addAttribute("user", userService.findUserById(userId));

    return "userDetails";
}

Meaning I only want to get the user's ID. For this, I created the following template:

<form th:each="user: ${users}" th:action="@{/users/{user_id}/details(user_id=${user.id}})}" method="post">
    <tr>
        <td th:text="${user.userName}">Name</td>
        <td><input type="submit" value="Details" /></td>
    </tr>
</form>

Now, this results in

Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "@{/users/{user_id}/details(user_id=${user.id}})}" (template: "users" - line 13, col 44)

I also tried the old post method with a hidden text field for the id and used th:object="${user.id}" in the controller it's null. I left the th:object on user, while having the sole hidden field on user.id, in the controller User is null id field is not set. If I try to use th:field for anything, I again get a thymeleaf exception:

Caused by: org.attoparser.ParseException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "users" - line 16, col 44)

My deepest greatest respect to every web developer there is out there. I always hated anything related to web development and honestly this doesn't want me to even start to like it. This is complete madness:)

Since you are using spring boot and thymeleaf.

You've to map the input to the model attribute with the input's name. So if you want to pass the id of an user, the input name has to be "user.id". You can do this manually, or the standard way, which is using th:field .

th:field will render the appropiate name for your input. It references the command object you set with th:object in the form element if you use *{...} syntax, so if your "User" object has an "id" attribute, you'd bind it with *{id} .

The way it will work in your current code would be

<form th:each="user : ${users} th:action="@{/user}" th:object="${user}" method="post">
  <tr>
    <td><input type="text" th:field="*{id}"></td>
    <td><input type="submit" value="Details"></td>
  </tr>
</form>

Edit: Nonetheless, as some people said in the comments, you should either save your full object in bound hidden inputs so you can recover it in the controller (if for some reason you need the full object there), or, since you are really only binding an id, use a String/Integer/whatever as your model attribute instead of an "User" object.

Edit2: You need to pass an "user" attribute if your th:fields use it.

@GetMapping("/users")
public String listUsers(Model model) {
    model.addAttribute("user", new User());
    return "users";
}

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