Announcement Announcement Module
Collapse
No announcement yet.
Binding selected items (multiple select box) Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Binding selected items (multiple select box)

    Hello,

    I'm actually really stuck with binding an n:m object graph to a multiple select box.

    Consider the following domain model (hibernate beans):

    Code:
    public class User
    {
        private Integer id;
        private String username;
        private List<UserRole> userRoles;
    
        // Standard setters and getters
    &#125;
    
    public class Role
    &#123;
        private Integer id;
        private String name;
    
        // Standard setters and getters
    &#125;
    
    public class UserRole
    &#123;
        private User user;
        private Role role;
    
        // Standard setters and getters
    &#125;
    As you see the n:m relation is based on two 1:n relations (User 1:n UserRole n:1 Role).

    In my form I want two multiple select boxes - one box should show all unassigned roles, the other one should show the assigned ones.
    JavaScript buttons should manage the assignment / disassignment (shift entries from the unassigned box to the assigned box, or the other way around).

    My problem is, that I don't really know how to manage this in a professional way. I'd know how to do that manually in onBind(), but I thought it would be more professional to handle that by a custom PropertyEditor. The first thing with a custom PropertyEditor which I don't know is, to which Class I should bind it (List.class, UserRole.class, Role.class). Furthermore I don't know how the html code should look like (the spring related part - spring:bind, spring:transform or whatever).

    I read (and mostly understand) "Spring in Action", "Pro Spring", " Professional Java Development with the Spring Framework", as well es the online documentation and tons of forum topics. Nevertheless I feel totally innocent how this problem could be handled. I found many forum topics about binding multiple select boxes, but I couldn't find one that really helped.


    I'd really much appreciate any help!


    Best

    Oliver

  • #2
    I spent a lot of time on the same issue and wasn't able to come up with a satisfactory answer. Here's my best understanding: Spring can only bind form data to complex object relationship through indexed types (List, Map), and the index must be a String. So in your example, you cannot have a List of UserRoles, you would have to have a List of UserRole IDs represented as Strings.

    If you want to do a Map you're really in trouble because, though you can write a custom property editor to convert between a String and an Object, and you can use a Map to index properties with a String index, you can't combine the two, having a Map with a String index that gets converted to an Object type. (I have put in a request for this capability and it's been accepted for version 1.3rc1: http://opensource.atlassian.com/proj...rowse/SPR-1204)

    Editing complex relationships like this by binding your domain object as the command class would then, in my opinion, require too many concessions to my domain model solely to support Spring MVC. As a result I chose to create a transfer object with an interface that mapped well to a form, and then in my controller I manually bind those values to my domain object; this is the same way you would end up doing it in Struts with an ActionForm.

    It might be that I'm missing the boat and there's a way to bind complex domain objects directly to a form without changing the object's interface radically, but I haven't found it yet.

    Comment


    • #3
      Thanks a lot for your comment, debradley. I really do much appreciate it!

      Comment


      • #4
        Same issue

        I am actually having the same issue and i haven't found an easy answer to my problem yet on this forum. I am willing to use as much as possible my domain model, but it indeed quite hard, as my current nested (Set of Sets...). I am considering writing a command class and to map it manually.
        The CustomCollectionEditor provides almost an answer, but I haven't been able to get to an implementation that works for me... :-(

        Any hints? Thanks!

        Nicolas

        Comment


        • #5
          Dear all,
          Similar problem...
          Please give us any advice.


          Thanks!
          Last edited by leonchen; Apr 18th, 2006, 07:19 AM.

          Comment


          • #6
            Hi ojs

            First thing I would suggest is to simplify the problem. Worry about doing a single one-to-many problem, i.e. User and User.getRoles().

            When you select more than one (checkboxes, multi-select etc.) role, your web browser submits a number of String values (role=valueA,role=valueB) which you need to convert into a collection of Roles. So, create a single property editor which converts from that String value into a Role.

            Rather than repeating it all here, read the following and let me know if you have any questions (note it is for checkboxes and arrays, but it should be transferable to multi-select and Sets ) : http://forum.springframework.org/sho...ght=checkboxes

            Comment


            • #7
              Originally posted by NicolasPeeters
              The CustomCollectionEditor provides almost an answer, but I haven't been able to get to an implementation that works for me... :-(
              Matt Raible wrote an excellent tutorial on how to use CustomCollectionEditor to deal with multiple selections.

              Sometimes if performance is a concern, I tend to take a slightly different approach though - instead of using CustomCollectionEditor.convertElement(), which causes one DAO call per element, I would directly extend PropertyEditorSupport, in which I parse the comma separated strings to ids, and load all the elements in one shot.

              Comment


              • #8
                Tricky problem... indeed

                We also extended the CustomCollectionEditor. It basically does the following:
                - retrieve the ID of an object instance
                - transform an instance and return its ID

                On the JSP side, I populate the checkboxes in the case their ID are present in the list of comma-separated values of ID's (or any synthetic key you might want to create) using a "fn:contains" function of JSTL. I realize this last trick might be a discussion point, but it's very concise (and my ID's are always unique), so we went for it...

                Code:
                <c:forEach items="${items}" var="item" varStatus="loopStatus">
                <spring:bind path="project.myItems">
                	<input type="hidden" name="_${status.expression}">
                             <input type="checkbox" name="${status.expression}"
                		value="<c:out value='${item.id}' />"
                	<c:if test="${fn:contains(status.value,item.id)}">checked</c:if> />
                	     		${sl.label} <br />
                </spring:bind>
                </c:forEach>
                Hope it helps!

                Comment


                • #9
                  Originally posted by NicolasPeeters
                  using a "fn:contains" function of JSTL. I realize this last trick might be a discussion point...
                  Under JSP 2, it's trivial to create an EL function to test for collection containment. I use referenceData to populate the model with all of the choices and a CustomCollectionEditor to bind the multiselect/checkbox group. Then, a simple
                  Code:
                  ${coll:contains(refItems,curItem)}
                  is all it takes to determine if an item should be selected/checked at render time.

                  I was surprised at just how easy. Here's the code for the contains function:
                  Code:
                  public class CollectionUtil {
                  
                  	public static boolean contains(Collection coll, Object item) {
                  		return coll.contains(item);
                  	}
                  
                  	private CollectionUtil() {
                  		super();
                  	}
                  
                  }
                  And the TLD:
                  Code:
                  <taglib
                  	xmlns="http://java.sun.com/xml/ns/j2ee"
                  	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                      http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
                  	version="2.0">
                  
                  	<description>Java collection support tag library</description>
                  
                  	<tlib-version>0.1</tlib-version>
                  	<short-name>coll</short-name>
                  	<uri>http://www.orthogony.com/ns/taglib/collections</uri>
                  
                  	<function>
                  		<description>
                  			Check for the existence of an object in a collection.
                  		</description>
                  		<name>contains</name>
                  		<function-class>
                  			com.orthogony.web.jsp.taglib.CollectionUtil
                  		</function-class>
                  		<function-signature>
                  			boolean contains(java.util.Collection, java.lang.Object)
                  		</function-signature>
                  	</function>
                  </taglib>
                  -dub

                  Comment


                  • #10
                    Dynamic binding to a HashSet

                    I have a slightly different problem.

                    I have a hibernate domain object as follows
                    Code:
                    public class TestModel 
                    {
                        Set s;
                    
                    ......getters and setters
                    }
                    The HashSet in the TestModel contains the following objects (say 4 of them)
                    Code:
                    public class City 
                    {
                    	private String city;
                            private String name;
                    
                    ......getters and setters
                    }
                    I use the formBackingObject to populate the values so that they get displayed on the form
                    Code:
                    protected Object formBackingObject(HttpServletRequest arg0) 
                                         throws  Exception 
                    {
                    		TestModel testModel = new TestModel();
                    
                    		Set s = new HashSet();
                    
                                    City city = new City();
                    		city.setCity("A");
                                    city.setName("B");
                    		s.add(city);
                    
                    		city = new City();
                    		city.setCity("C");
                                    city.setName("D");
                    		s.add(city);
                    
                    		city = new City();
                    		city.setCity("E");
                                    city.setName("F");
                    		s.add(city);
                    
                    		city = new City();
                    		city.setCity("G");
                                    city.setName("H");
                    		s.add(city);
                    
                         testModel.setS(s);
                         
                         return testModel ;
                    }

                    The jsp page uses this code to bind and show the values

                    HTML Code:
                    	<spring:bind path="testModel.s">
                    		<core:forEach items="${status.value}" var="obj">
                    		    <input type="text" value="<core:out value="${obj.city}"/>"  name="<core:out value="${status.expression}"/>City">
                                          
                    
                                        <input type="text" value="<core:out value="${obj.name}"/>"  name="<core:out value="${status.expression}"/>Name">
                    		
                                    </core:forEach>
                    		
                    		<div id="myDiv">
                    			<input type="hidden" value="0" id="theValue" />
                    		</div>
                    
                    			<tr>
                    			<input type="button" name="addRow" value="add" onclick="addElement('<core:out value="${status.expression}"/>City','<core:out value="${status.expression}"/>Name');">
                    		</tr>
                    		
                    		
                    	</spring:bind>
                    After getting displayed, the jsp now contains




                    The values within the objects in the "languages" HashSet are displayed in the jsp as

                    City Name
                    --- -----
                    A B
                    C D
                    E F
                    G H

                    ----------
                    | AddRow | <-- The add row button
                    ----------

                    The actual "names" of the textboxes for City and Name come out to be "sCity" and "sName" respectively.
                    By using the "addRow" another row is dynamically inserted to the table with the same names as the previous textboxes. Within the jsp these new textboxes come within the <spring:bind> tag.

                    But when the form is submitted these do not show up in the model. Which means that, if there are already 4 cities in the model and i add a fifth dynamically, the fifth is not shown when displayed within the onSubmit() method after being extracted from the model.

                    Code:
                    protected ModelAndView onSubmit(HttpServletRequest arg0,
                    			HttpServletResponse arg1, Object arg2, BindException arg3)
                    			throws Exception 
                    {
                          TestModel testModel = (TestModel) arg2;
                          System.out.println("hash SIZE = " + tm.getS().size());
                    
                    .....
                    }
                    The size is still shown as 4 instead of 5.

                    I have been searching without success in this forum for a solution to this problem .

                    What am I doing wrong ?

                    Someone help me out on this.

                    Comment

                    Working...
                    X