Announcement Announcement Module
Collapse
No announcement yet.
How to create and use checkbox list Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to create and use checkbox list

    Hi Spring lovers,

    I'm still writing my first spring application. It's running fine now with Hibernate for the persistence, Acegi for Security and Spring MVC for the frontend. I tried to merge what I like best in the jpetstore and petclinic sample apps.

    Now I want to let administrators set/unset authorities for the principals/users by getting a list of available authorities from the database, generate and display the checkboxes and then store the new set of authorities for a given user.

    I've found the following jsp-code for the frontend which is exactly what I need (I guess):

    Code:
      <c:forEach items="${command.childArray}" var="child" varStatus="loopStatus">
         <spring:bind path="command.childArray[${loopStatus.index}].selected"> 
            <input type="hidden" name="_<c:out value="${status.expression}"/>">
            <input type="checkbox" name="<c:out value="${status.expression}"/>" value="true"
                <c:if test="${status.value}">checked</c:if>/>
         </spring:bind>
       </c:forEach>
    But I'm a little bit confused how to use it. Is there any rough example for the controller on how to setup the formBackingObject and how to setup the submit method and retrieve the checked list of authorities from the form?

    Any help would be appreciated.

    Cheers,
    Thomas
    Last edited by ThomasBecker; Dec 14th, 2005, 08:36 AM.

  • #2
    Maybe this will help (shameless self promotion ) http://forum.springframework.org/sho...ght=checkboxes

    Comment


    • #3
      Hi

      Check out the following wiki entry Working with Checkboxes. There's lots of goodies to be found there.

      Comment


      • #4
        Hey Yatesco, Hello Jeff,

        thanks for your fast replies. I actually have it nearly working, thank you both. The code presented in the Wiki by Eu Gene Lim was the code I pasted in my first post.
        I like it, but have some struggles to get it running as expected, since I'm quite new to spring and not the experiencest Java Programmer.

        Right now I'm on a good way to get it done. When I'm finished I will post the controllers Code and the final JSP Code as well to contribute something to the spring community.

        Cheers guys and thanks again!
        Thomas

        Comment


        • #5
          Keep asking those questions Asha'man

          Comment


          • #6
            What do you mean? Easy to answer questions?

            Comment


            • #7
              Originally posted by Asha'man
              What do you mean? Easy to answer questions?
              Nope, I mean keep asking any questions.

              Of course, if you do ask easy ones, then I will answer, but the harder ones will be left to others

              Comment


              • #8
                The story goes on.

                I got the formBackingObject working as it should, the list of available checkboxes is generated dynamically and checkboxes which should be preselected are.

                Here's the code of the controller. At the end I will post some more generic jsp and code for other beginners like I'am facing the same problems as I do. I don't know if this is the best way to do it, actually I think there's room for improvement, so any feedback is highly appreciated:

                Code:
                package com.vodafone.web;
                
                import java.util.Collection;
                
                public class AuthorityFormController extends SimpleFormController {
                
                    /** Logger for this class and subclasses */
                    protected final Log logger = LogFactory.getLog(getClass());
                
                    private UserManager userManager;
                    private String cryptedPassword;
                    
                	public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
                								 Object command, BindException errors)
                            throws ServletException {
                    	UserBean user = (UserBean) command;
                        userManager.storeUser(user);
                        return new ModelAndView(new RedirectView(getSuccessView()));
                    }
                
                    protected Object formBackingObject(HttpServletRequest request) throws ServletException {
                    	Integer id = RequestUtils.getIntParameter(request, "userId");
                    	UserBean user = userManager.getUserById(id);
                    	List authList = userManager.getAuthorityList();
                    	Collection userAuthorities = new LinkedHashSet();
                    	
                    	// Start iterating the list of all possible Authorities
                    	if (authList.size()>0){
                    		Iterator it = authList.iterator();
                	    	while (it.hasNext()){
                	    		AuthorityBean ab = (AuthorityBean)it.next();;
                	    		/* Get a list with all authorities this user has set. Then start an inner loop and set
                	    		   the selected flag to false for all authorities, which are not set for this user already */
                	    		Iterator it2 = user.getAuthorities().iterator();
                	    		while (it2.hasNext()){
                	    			AuthorityBean userab = (AuthorityBean)it2.next();
                	    			logger.info("Comparing: userab " + userab.getAuthority() + " with ab " + ab.getAuthority() );
                	    			if (!userab.getAuthority().equals(ab.getAuthority())){
                	    				ab.setSelected(false);
                	    			}else{
                	    				// If a match was found, we set AuthorityBean.setSelected and leave go on with the next bean
                	    				ab.setSelected(true);
                	    				break;
                	    			}
                	    		}
                	    		userAuthorities.add(ab);
                	    	}
                    	}
                    	user.setAuthorities((Set)userAuthorities);
                    	
                    	logger.info(user.getLogin() + " auth: " + user.getAuthorities().size());    	
                    	return user;
                    }
                
                    public void setUserManager(UserManager userManager) {
                        this.userManager = userManager;
                    }
                }
                Here's my JSP:

                Code:
                <%@ include file="/WEB-INF/jsp/include.jsp" %>
                <%@ include file="/WEB-INF/jsp/header.jsp" %>
                
                <P>
                <H2><fmt:message key="prefix.admin"/><fmt:message key="heading.authority"/></H2>
                <p>
                <H3>Set authorities for: <c:out value="${user.login}"/></H3>
                <spring:bind path="user">
                  <FONT color="red">
                    <B><c:out value="${status.errorMessage}"/></B>
                  </FONT>
                </spring:bind><p>
                <FORM method="POST">
                <c:forEach items="${user.authorities}" var="authority" varStatus="loopStatus">
                  <spring:bind path="user.authorities[${loopStatus.index}].selected"> 
                    <c:out value="${authority.authority}"/> 
                    <input type="hidden" name="_<c:out value="${status.expression}"/>">
                    <input type="checkbox" name="<c:out value="${status.expression}"/>" value="true"
                      <c:if test="${status.value}">checked</c:if>/>
                  </spring:bind>
                </c:forEach>
                <BR><INPUT type = "submit" value="Set authorities"/>
                </FORM>
                <BR>
                
                <%@ include file="/WEB-INF/jsp/footer.jsp" %>
                I really enjoy the way spring does many things!

                Ok, I'm missing now only one step. I don't have a clue how to retrieve the result of the submitted form? Since it's an array inside the form, I don't have a clue how to use RequestUtils here properly? Is using RequestUtils the wrong way???

                Comment


                • #9
                  Why are you not simply doing command.yourAccessor()?

                  Just out of interest, did you not my original link helpful as I thought it already answered some of your questions. If you didn't, please comment and I will amend the original link

                  Comment


                  • #10
                    It was actually already working. I just didn't know.

                    This is the complete code of the Controller:

                    Code:
                    package com.vodafone.web;
                    
                    import java.util.Collection;
                    
                    public class AuthorityFormController extends SimpleFormController {
                    
                        /** Logger for this class and subclasses */
                        protected final Log logger = LogFactory.getLog(getClass());
                    
                        private UserManager userManager;
                        
                    	public ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response,
                    								 Object command, BindException errors)
                                throws ServletException {
                        	UserBean user = (UserBean) command;
                        	Collection submittedAuthorities = new HashSet();
                        	// Create a new Set with the authorities which were actually submitted
                        	if (user.getAuthorities().size()>0){
                    	    	Iterator it = user.getAuthorities().iterator();
                    	    	while (it.hasNext()){
                    	    		AuthorityBean ab = (AuthorityBean)it.next();
                    	    		logger.info(ab.getAuthority() + " " + ab.isSelected());
                    	    		if (ab.isSelected()){
                    	    			submittedAuthorities.add(ab);
                    	    		}
                    	    	}
                        	}
                        	user.setAuthorities((Set)submittedAuthorities);
                        	// now store the user with the new authorities set. Now the new roles are granted to the user.
                            userManager.storeUser(user);
                            return new ModelAndView(new RedirectView(getSuccessView()));
                        }
                    
                        protected Object formBackingObject(HttpServletRequest request) throws ServletException {
                        	Integer id = RequestUtils.getIntParameter(request, "userId");
                        	UserBean user = userManager.getUserById(id);
                        	List authList = userManager.getAuthorityList();
                        	Collection userAuthorities = new LinkedHashSet();
                        	
                        	// Start iterating the list of all possible Authorities
                        	if (authList.size()>0){
                        		Iterator it = authList.iterator();
                    	    	while (it.hasNext()){
                    	    		AuthorityBean ab = (AuthorityBean)it.next();;
                    	    		/* Get a list with all authorities this user has set. Then start an inner loop and set
                    	    		   the selected flag to false for all authorities, which are not set for this user already 
                    	    		   This is necessary to have a checkbox for all available authorities and not only the authorities 
                    	    		   this user already has. I feel this is a little bit dirty, but don't know how to improve?
                    	    		   */
                    	    		Iterator it2 = user.getAuthorities().iterator();
                    	    		while (it2.hasNext()){
                    	    			AuthorityBean userab = (AuthorityBean)it2.next();
                    	    			logger.info("Comparing: userab " + userab.getAuthority() + " with ab " + ab.getAuthority() );
                    	    			if (!userab.getAuthority().equals(ab.getAuthority())){
                    	    				ab.setSelected(false);
                    	    			}else{
                    	    				// If a match was found, we set AuthorityBean.setSelected and leave go on with the next bean
                    	    				// The checkbox for this authority will then be preselected
                    	    				ab.setSelected(true);
                    	    				break;
                    	    			}
                    	    		}
                    	    		userAuthorities.add(ab);
                    	    	}
                        	}
                        	// The user gets temporarily all authorities set to have a checkbox for each authority --> nasty :(
                        	// The user is not stored at this point, so he's not granted any more authorities than he had before
                        	user.setAuthorities((Set)userAuthorities);
                        	
                        	logger.info(user.getLogin() + " auth: " + user.getAuthorities().size());    	
                        	return user;
                        }
                    
                        public void setUserManager(UserManager userManager) {
                            this.userManager = userManager;
                        }
                    }
                    Here's the jsp:

                    Code:
                    <%@ include file="/WEB-INF/jsp/include.jsp" %>
                    <%@ include file="/WEB-INF/jsp/header.jsp" %>
                    
                    <P>
                    <H2><fmt:message key="prefix.admin"/><fmt:message key="heading.authority"/></H2>
                    <p>
                    <H3>Set authorities for: <c:out value="${user.login}"/></H3>
                    <spring:bind path="user">
                      <FONT color="red">
                        <B><c:out value="${status.errorMessage}"/></B>
                      </FONT>
                    </spring:bind><p>
                    <FORM method="POST">
                    <c:forEach items="${user.authorities}" var="authority" varStatus="loopStatus">
                      <spring:bind path="user.authorities[${loopStatus.index}].selected"> 
                        <c:out value="${authority.authority}"/> 
                        <input type="hidden" name="_<c:out value="${status.expression}"/>">
                        <input type="checkbox" name="<c:out value="${status.expression}"/>" value="true"
                          <c:if test="${status.value}">checked</c:if>/>
                      </spring:bind>
                    </c:forEach>
                    <BR><INPUT type = "submit" value="Set authorities"/>
                    </FORM>
                    <BR>
                    
                    <%@ include file="/WEB-INF/jsp/footer.jsp" %>
                    If there's demand, I'd be happy to write some more generic example for the community. Just let me know.

                    @yatesco: I didn't do it the way you described, because I already started the other way and when I first saw your FAQ entry, I had almost half of the story already finished.

                    Comment


                    • #11
                      What about a java.util.Set?

                      Yatesco,

                      Can you use the same code if you are binding to a java.util.Set?
                      I guess this part won't work too well with a Set...
                      Code:
                       <spring:bind path="user.authorities[${loopStatus.index}].selected">
                      Any known workarounds?

                      Nicolas

                      PS: I was planning to write a custom property editor to look up the real object (using the ID)...

                      Comment


                      • #12
                        Good call on the propertyEditor

                        No, unfortunately (as far as I know) the Spring binding process won't create a Set but it should be easy enough to create one:

                        Code:
                          public class SetPropertyEditor extends PropertyEditorSupport {
                            abstract String convertObjectToString(final Object obj);
                            abstract Object convertStringToObject(final String s);
                        
                            public final String getAsText(final Object obj) {
                              Set set = (Set) getValue();
                              String[] values = new String[set.size()];
                              int counter =0;
                              for (Iterator i = set.iterator(); i.hasNext(); counter++) {
                                values[counter] = convertObjectToString(i.next());
                              }
                            }
                        
                            public final void setAsText(final Object obj) {
                              Set set = new HashSet();
                              String[] values = toStringArray(obj);
                              for (int i=0; i<values.length; i++) {
                                set.add(convertStringToObject(values[i]);
                              }
                              super.setValue(set);
                            }
                            private String[] toStringArray(final Object o) {
                              if (o == null) {
                                return new String[] {};
                              }
                        
                              if (o instanceof String) {
                                return new String[] {(String) o};
                              } else if (o instance of String[]) {
                                return (String[]) o;
                              } else {
                                throw new IllegalStateException("Cannot handle objects of type " + o.getClass() + ", only String or String[]");
                              }
                            }
                          }
                        and one possible implementation would be:

                        Code:
                          public final class YourObjectSetPropertyEditor {
                             private final YourDAO dao;
                             public YourObjectSetPropertyEditor(final YourDAO theDAO) {
                               super();
                               this.dao = theDAO;
                             }
                        
                            public String convertObjectToString(final Object obj) {
                              ((YourObject) obj).getSomePropertyWhichRepresentsTheObject();
                            }
                        
                            public Object convertStringToObject(final String s) {
                              return dao.findById(s);
                            }
                          }
                        This would have to be registered against the property name, not Set.class:

                        Code:
                          initBinder(...) {
                            binder.registerCustomEditor(Set.class, "yourProperty", new YourObjectSetPropertyEditor(yourDAO);
                          }
                        I typed all this in, so it almost certainly won't work, but you get the idea?

                        Comment


                        • #13
                          Thanks a million for your fantastically fast answer! I have to implement that, I'll definitely post my findings about your solution in this thread.

                          For the property editor, I found something in the forum with the following option. The amount of code is significantly reduced. Is there a problem with this solution (compared to the one you just proposed)?

                          Code:
                          protected void initBinder(RequestContext context, DataBinder binder) {	
                          //...
                          binder.registerCustomEditor(Set.class, "myChildren", new CustomCollectionEditor(Set.class) {
                          	
                          	protected Object convertElement(Object element) {
                          		Child myChild = null;
                          		if (element != null) {
                          			Long id = new Long((String)element);
                          			myChild = (Child) lookupService.getById(id);
                          		}
                          		return myChild;
                          	}
                          });
                          }

                          Comment


                          • #14
                            Probably not. I am not familar with CustomCollectionEditor, but it is probably similar to my naive SetPropertyEditor

                            Comment


                            • #15
                              I would probably read the javadoc (http://www.springframework.org/docs/...ionEditor.html) though because you will almost certainly want a sane getAsText() method

                              I *knew* Spring would have already had something

                              Comment

                              Working...
                              X