Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC Pre Populate Checkboxes

First little background info. Got a fairly standard User Role relationship where the User can have many roles. I have roles defined as a set within the user class. Now I know that html forms have all the values as strings and trying to get values as my custom Role object does not work. I implemented an initbinder to convert the id's back into object so that I can retrieve the selected values off of my checkboxes, that part works.

But I can't seem to go back the other way. I retrieve a User from the database that already has roles and want to pre populate role checkboxes with all the roles that a user has. Based on this example :

Checkboxes example

They say that:

form:checkboxes items="${dynamic-list}" path="property-to-store"

For multiple checkboxes, as long as the “path” or “property” value is equal to any of the “checkbox values – ${dynamic-list}“, the matched checkbox will be checked automatically.

My interpretation of that is I should be able to feed it a Set of all the roles and define the path to be the roles from the User object and it should match them thus causing the check box to pre populate.

Every example out there seems to have the value of dynamic-list as a String[]. Well thats great and dandy but how does this work for custom objects that our defined as a Set? Can I still use this one line definition for checkboxes or do I need to do some kind of data binding heading into the view also?

Here is my user dto, user controller, custom form binder, and user edit page.

User DTO

@Entity
@Table
public class User extends BaseDto
{
   @Column(updatable = false) @NotBlank
   private String username;

   @Column(name = "encrypted_password") @Size(min = 6, message = "password must be at least 6 characters") @Pattern(regexp = "^\\S*$", message = "invalid character detected")
   private String password;

   @Column(name = "first_name")
   private String firstName;

   @Column(name = "last_name")
   private String lastName;

   @Column @NotNull
   private boolean enabled;

   @Column @Email @NotBlank
   private String email;

   @Transient
   private String confirmPassword;

   @ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER, cascade = CascadeType.REFRESH) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"),
      inverseJoinColumns = @JoinColumn(name = "role_id"))
   private Set<Role> roles;

   public User()
   {
   }

   public User(final String usernameIn, final String passwordIn, final String firstNameIn, final String lastNameIn, final String emailIn, final boolean enabledIn)
   {
      username = usernameIn;
      password = passwordIn;
      firstName = firstNameIn;
      lastName = lastNameIn;
      email = emailIn;
      enabled = enabledIn;
   }

   public String getUsername()
   {
      return username;
   }

   public void setUsername(final String usernameIn)
   {
      username = usernameIn;
   }

   public String getPassword()
   {
      return password;
   }

   public void setPassword(final String passwordIn)
   {
      password = passwordIn;
   }

   public String getFirstName()
   {
      return firstName;
   }

   public void setFirstName(final String firstNameIn)
   {
      firstName = firstNameIn;
   }

   public String getLastName()
   {
      return lastName;
   }

   public void setLastName(final String lastNameIn)
   {
      lastName = lastNameIn;
   }

   public String getEmail()
   {
      return email;
   }

   public void setEmail(final String emailIn)
   {
      email = emailIn;
   }

   public String getConfirmPassword()
   {
      return confirmPassword;
   }

   public void setConfirmPassword(final String confirmPasswordIn)
   {
      confirmPassword = confirmPasswordIn;
   }

   public boolean isEnabled()
   {
      return enabled;
   }

   public void setEnabled(final boolean enabledIn)
   {
      enabled = enabledIn;
   }

   public Set<Role> getRoles()
   {
      return roles;
   }

   public void setRoles(final Set<Role> rolesIn)
   {
      roles = rolesIn;
   }
}

User Controller

@Controller @RequestMapping("/user")
public class UserController
{
   @Autowired private UserService userService;
   @Autowired private UserDao userDao;
   @Autowired private RoleDao roleDao;

   @InitBinder
   public void bindForm(final WebDataBinder binder)
   {
      binder.registerCustomEditor(Set.class, "roles", new CustomFormBinder<RoleDao>(roleDao, Set.class));
   }

   @RequestMapping(method = RequestMethod.GET)
   public String index(final ModelMap modelMap)
   {
      return "/user/index";
   }

   @RequestMapping(value = "/create", method = RequestMethod.GET)
   public String create(final ModelMap modelMap)
   {
      modelMap.addAttribute("userInstance", new User());
      modelMap.addAttribute("validRoles",  new HashSet<Role>(roleDao.findAll()));

      return "/user/create";
   }

   @RequestMapping(value = "/save", method = RequestMethod.POST)
   public String save(final ModelMap modelMap, @Valid @ModelAttribute("userInstance") final User user, final BindingResult bindingResult)
   {
      // TODO move to service validation
      if (user.getPassword() == null || !user.getPassword().equals(user.getConfirmPassword()) )
      {
         bindingResult.addError(new FieldError("userInstance", "password", "password fields must match"));
         bindingResult.addError(new FieldError("userInstance", "confirmPassword", "password fields must match"));
      }
      if (user.getRoles() == null || user.getRoles().isEmpty())
      {
         bindingResult.addError(new FieldError("userInstance", "roles", "Must select at least one role for a User"));
      }
      if (bindingResult.hasErrors())
      {
         modelMap.addAttribute("validRoles",  new HashSet<Role>(roleDao.findAll()));
         return "/user/create";
      }

      userService.save(user);
      return "redirect:/user/list";
   }

   @RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
   public String edit(@PathVariable final Integer id, final ModelMap modelMap)
   {
      final User user = userDao.find(id);

      if (user != null)
      {
         modelMap.addAttribute("userInstance", user);
         modelMap.addAttribute("validRoles",  new HashSet<Role>(roleDao.findAll()));
         return "/user/edit";
      }
      return "redirect:/user/list";
   }

   @RequestMapping(value = "/edit", method = RequestMethod.GET)
   public String editCurrent(final ModelMap modelMap)
   {
      return edit(userService.getLoggedInUser().getId(), modelMap);
   }

   @RequestMapping(value = "/update", method = RequestMethod.POST)
   public String update(@Valid @ModelAttribute("userInstance") final User user, final BindingResult bindingResult)
   {
      if (bindingResult.hasErrors())
      {
         return "/user/edit";
      }

      userService.save(user);
      return "redirect:/user/list";
   }

   @ModelAttribute("userInstances")
   @RequestMapping(value = "/list", method = RequestMethod.GET)
   public List<User> list()
   {
      return userDao.findAll();
   }
}

Custom Form Binder

public class CustomFormBinder<T extends GenericDao> extends CustomCollectionEditor
{
   private final T dao;

   private static final Logger LOG = LoggerFactory.getLogger(CustomFormBinder.class);

   public CustomFormBinder(final T daoIn, final Class collectionType)
   {
      super(collectionType, true);
      dao = daoIn;
   }

   @Override
   protected Object convertElement(final Object element)
   {
      try
      {
         // forms should return the id as the itemValue
         return dao.find(Integer.valueOf(element.toString()));
      }
      catch (NumberFormatException e)
      {
         LOG.warn("Unable to convert " + element + " to an integer");
         return null;
      }
   }

}

User Edit View

    <html>
    <head>
        <title>Create User</title>
    </head>
    <body>

    <c:url value="/user/update" var="actionUrl"/>
    <form:form method="post" commandName="userInstance" action="${actionUrl}">
        <h1>Edit User ${userInstance.username}</h1>

        <div>
            <form:label path="username">Username:</form:label>
            <form:input path="username" id="username" readonly="true"/>
        </div>

        <div>
            <form:label path="password">Password:</form:label>
            <form:input path="password" id="password" type="password" readonly="true"/>
            <tag:errorlist path="userInstance.password" cssClass="formError"/>
        </div>

        <div>
            <form:label path="firstName">First Name:</form:label>
            <form:input path="firstName" id="firstName"/>
            <tag:errorlist path="userInstance.firstName" cssClass="formError"/>
        </div>

        <div>
            <form:label path="lastName">Last Name:</form:label>
            <form:input path="lastName" id="lastName"/>
            <tag:errorlist path="userInstance.lastName" cssClass="formError"/>
        </div>

        <div>
            <form:label path="email">Email:</form:label>
            <form:input path="email" id="email" size="30"/>
            <tag:errorlist path="userInstance.email" cssClass="formError"/>
        </div>

        <div>
         **<%--Want to Pre Populate these checkboxed--%>   
<form:checkboxes title="Assigned Roles:" path="roles" id="roles" items="${validRoles}" itemLabel="displayName" itemValue="id" element="div"/>**
            <tag:errorlist path="userInstance.roles" cssClass="formError"/>
        </div>

        <form:hidden path="enabled"/>
        <form:hidden path="id"/>
        <form:hidden path="version"/>

        <div class="submit">
            <input type="submit" value="Update"/>
            <a href="<c:url value='/user/list'/>" class="cancel">Cancel</a>
        </div>
    </form:form>

    </body>
    </html>
like image 566
sauce Avatar asked Aug 08 '11 23:08

sauce


1 Answers

You need a correct implemented equals method for Role!

If this is not enough have a look at class oorg.springframework.web.servlet.tags.form.AbstractCheckedElementTag. The method void renderFromValue(Object item, Object value, TagWriter tagWriter) is where the the checked flag is set.

like image 137
Ralph Avatar answered Nov 08 '22 06:11

Ralph