I'm trying to write a UI for user management and one of the important things is ability to change user roles.
Model is simple, user belongs to many roles, role has many users.
The code below presents self contained @Controller for editing existing user:
@Controller
public class NewController {
final Role r1 = new Role("admin");
final Role r2 = new Role("user");
final Role r3 = new Role("editor");
final List<Role> allRoles = Arrays.asList(r1, r2, r3);
final List<Role> userRoles = Arrays.asList(r3);
@RequestMapping(value = "/edit", method = RequestMethod.GET)
public ModelAndView editUser() {
final User user = new User();
user.setEmail("[email protected]");
user.setUsername("username");
user.setAuthorities(userRoles);
final ModelAndView mav = new ModelAndView("edit");
allRoles.stream()
.filter(r-> user.getAuthorities().contains(r))
.forEach(r -> r.setChecked(true));
mav.addObject("roles", allRoles);
mav.addObject("user", user);
return mav;
}
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public ModelAndView updateUser(
@ModelAttribute("user") User user,
@ModelAttribute("roles") ArrayList<Role> roles,
ModelMap model) {
final List<Role> newRoles = roles.stream()
.filter(r -> r.isChecked() != null)
.filter(r -> r.isChecked() == true)
.collect(Collectors.toList());
user.setAuthorities(newRoles);
return new ModelAndView("redirect:/edit");
}
}
And that's my Spring-Thymeleaf page where I'm able to edit the user:
<form th:action="'/edit'" method="POST" enctype="utf8">
<div class="form-group row">
<label class="col-sm-3" th:text="#{label.user.id}">Id</label>
<span class="col-sm-5" th:text="${user.id}">id</span>
</div>
<div class="form-group row">
<label class="col-sm-3" th:text="#{label.user.username}">Username</label>
<input class="form-control" name="username" th:value="${user.username}" required="required"/></span>
</div>
<div class="form-group row">
<label class="col-sm-3" th:text="#{label.user.email}">email</label>
<input type="email" class="form-control" name="email" th:value="${user.email}"
required="required"/></span>
</div>
<div class="form-group row">
<label class="col-sm-3" th:text="#{label.user.password}">password</label>
<input id="password" class="form-control" name="password" value=""
type="password"/></span>
</div>
<div class="form-group row">
<label class="col-sm-3" th:text="#{label.user.confirmPass}">confirm</label>
<input id="matchPassword" class="form-control" name="matchingPassword" value=""
type="password"/></span>
</div>
<div th:each="item, index : ${roles}">
<input type="checkbox"
th:text="${item.authority}"
name="${item.authority}"
th:checked="${item.checked}" />
</div>
<button type="submit" class="btn btn-primary" th:text="#{label.edit.save}">save</button>
</form>
When I click 'Save' button, updateUser
method gets triggered, but ArrayList roles
is empty. Other values for User model are passed correctly, I can change username/email/password without problems, user bean is populated correctly.
Ok, solved it differently:
Fragment of the HTML page with checkbox handling should be:
<th:block th:each="role : ${roles}">
<input type="checkbox"
name="newRoles"
th:checked="${role.checked}"
th:value="${role.id}"/>
<label th:text="${role.authority}"></label>
</th:block>
So when you submit the form, you get list of Role ids inside newRoles RequestParam, and that's my POST controller:
@RequestMapping(value = "/users/{id}/edit", method = RequestMethod.POST)
public ModelAndView updateUser(
@ModelAttribute("user") User user,
@RequestParam(value = "newRoles") ArrayList<Long> roles,
BindingResult bindingResult , Model model) {
final List<Role> newRoles =
roles.stream()
.map(id -> roleService.findOne(id))
.collect(Collectors.toList());
user.setAuthorities(newRoles);
userService.update(user);
return new ModelAndView("redirect:/users");
}