Announcement Announcement Module
Collapse
No announcement yet.
How do i bind nested model objects to form fields? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • How do i bind nested model objects to form fields?

    Hi,

    I have a problem with Spring's form binding:

    Say I have a "CAR" model class passed to a jsp form and this class has a property which is a "Driver" class. How are we supposed to tell Spring to bind jsp form field this sub-object??? I get errors whenever i try to submit the form.

    Does it have something to do with propertyEditors? Or are we constrained in having all attributes of the model be simple data types?

    Any help would be appreciated!

    Uze

  • #2
    Using initBinder() in the form controller (or whatever other command controller) you can register your own custom property editors. Using those you can resolve other than primitive types.

    Implement PropertyEditorSupport and override setAsText and getAsText:

    Code:
    DriverEditor extends PropertyEditorSupport {
    
      public void setAsText(String text) {
        // text is the string from the form, for example an identifier in
        // the database
        
        // use whatever you like to retrieve the appropriate Driver object
        Driver d; // retrieve
        setValue(d);
      }
    
      public String getAsText(Object value) {
        Driver d = (Driver)value);
        return d.getId(); // for example
      }
    }
    Let's say your command name is car.

    Code:
    <select name="car.driver">
      <option value="1">Driver one</option>
      <option value="2">Driver two</option>
    </select>
    1 resp. 2 would be the String passed to your custom editor.

    Finally, register your editor using

    Code:
    binder.registerCustomEditor&#40;Driver.class, "driver" /* this is optional */, new DriverEditor&#40;&#41;&#41;;
    Hope this helps.

    Alef

    Comment


    • #3
      You probably need to initialize the nested object in its parent. For example:

      Code:
      public class Car &#123;
      
          private Driver driver = new Driver&#40;&#41;;
      
      &#125;

      Comment


      • #4
        Originally posted by mraible
        You probably need to initialize the nested object in its parent. For example:

        Code:
        public class Car &#123;
        
            private Driver driver = new Driver&#40;&#41;;
        
        &#125;
        Yes of course, if you need to bind a primitive value to the driver property of the car object.

        Code:
        <input type="text" name="car.driver.name">
        You wouldn't need to bind customer editors in this cas.e

        Alef

        Comment


        • #5
          Thanks a lot Alef, this is what I was looking for. However, where do you call the binder.registerCustomEditor? Is it to be called in a specific method of a controller servlet (ie initBinder) or declared in the xyz-servlet.xml config file under a form bean definition? A short example would help a lot.

          Thanks again!

          Uze

          Comment


          • #6
            However, where do you call the binder.registerCustomEditor? Is it to be called in a specific method of a controller servlet (ie initBinder)
            initBinder is the method you're looking for. If you read the JavaDoc for BaseCommandController, esp. the initBinder method, you should find all you need to know. The references to registerCustomEditor are also helpful.

            Comment


            • #7
              So far so good, however I was more looking at a global definition, avoiding the need to register the same editors over and over again in many controllers. From the Spring doc, it appears to be doable in the application context with something like:

              Code:
              	 <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
              	   <property name="customEditors">
              	     <map>
                 
              	       <entry key="mypackage.Driver">
              	         <bean class="mypackage.UserEditor" />
              	       </entry>
              	       
              	     </map>
              	   </property>
              	 </bean>
              but it does not seem to pick it up and my custom editor is never called.

              Any ideas????

              Comment


              • #8
                The CustomEditorConfigurers only apply to property values used in the application context, so they don't get registered in your controllers. You have to register them yourself, either by doing that programmatically in every controller or by subclassing the command controller you're using and creating a simple structure yourself in which you can mention property editors.

                Remember that property editors and not threadsafe, so if you decide to go and implement somsething yourself that registers default property editors in all your controller, keep in mind that you need to create them over and over again instead of re-using one instance.

                Alef

                Comment


                • #9
                  Data access in property editor

                  This thread has been informative. However, I'm still a little confused about property editors. I've got a business object, Job, that is selected from the UI by ID. However, my property editor needs access to the database to resolve the ID to a Job object. I've configured the following bean in *-servlet.xml where the appConfig bean has all of the data access stuff in it.
                  Code:
                  	<bean id="jobPropertyEditor" class="com.myapp.util.propertyEditors.JobEditor">
                  	    <property name="appConfig">
                  			<ref bean="appConfig"/>
                  		</property>
                  	</bean>
                  In my controller I register my editor as such
                  Code:
                  protected void initBinder&#40;HttpServletRequest request, ServletRequestDataBinder binder&#41;
                          throws Exception &#123;
                      binder.registerCustomEditor&#40;Job.class, "job", &#40;JobEditor&#41; getApplicationContext&#40;&#41;.getBean&#40;
                              "jobPropertyEditor"&#41;&#41;;
                  &#125;
                  Is this the correct way to wire things up? It seems to work. Any guidance would be much appreciated.

                  - Justin

                  Comment


                  • #10
                    Originally posted by Alef Arendsen
                    Originally posted by mraible
                    You probably need to initialize the nested object in its parent. For example:

                    Code:
                    public class Car &#123;
                        private Driver driver = new Driver&#40;&#41;;
                    &#125;
                    Yes of course, if you need to bind a primitive value to the driver property of the car object.
                    Code:
                    <input type="text" name="car.driver.name">
                    You wouldn't need to bind customer editors in this cas.e
                    Alef
                    ===================================
                    Alef,

                    I'm kind of new to spring and am struggling with this same issue. Can you explain why you don't need a property editor in this case when you bind a primitive value to the driver property in the car object? What differentiates these 2 cases?

                    Also, why does Matt's solution work? I implemented it and it does wortk, but I want to understand what's going on here. It seems kind of strange that one needs to create a new, basically empty enclosed class [in this case, the Driver object] when there wopn't be any real values in that class anyway, since it's just getting created without any values when the class is loaded.

                    Thanks much!

                    -=j=-

                    [jack]

                    Comment


                    • #11
                      I'm kind of new to spring and am struggling with this same issue. Can you explain why you don't need a property editor in this case when you bind a primitive value to the driver property in the car object? What differentiates these 2 cases?

                      Also, why does Matt's solution work? I implemented it and it does wortk, but I want to understand what's going on here. It seems kind of strange that one needs to create a new, basically empty enclosed class [in this case, the Driver object] when there wopn't be any real values in that class anyway, since it's just getting created without any values when the class is loaded.
                      From what I can understand, the latter example is if you need to modify a primitive type property of a nested class. Alef's example was to change the *whole* nested class from a binded, primitive type form field. The PropertyEditor is required to tell spring how to "cast" a form string (that may represent a driver id for instance) to a new class instance.

                      Alef, I still struggle to understand your last post. As I am using a SimpleFormController, i created a subclass from it and implemented the initBinder with my propertyEditors. I extended my controllers from it as you said but that initBinder get call over and over again. Is there any way to declare and registers those in the application context so they are registered only once per session and called wheever they are required? If so, could you provide some sample code/xml?

                      Best regards,

                      Uze

                      Comment


                      • #12
                        Originally posted by uze
                        I'm kind of new to spring and am struggling with this same issue. Can you explain why you don't need a property editor in this case when you bind a primitive value to the driver property in the car object? What differentiates these 2 cases?

                        Also, why does Matt's solution work? I implemented it and it does wortk, but I want to understand what's going on here. It seems kind of strange that one needs to create a new, basically empty enclosed class [in this case, the Driver object] when there wopn't be any real values in that class anyway, since it's just getting created without any values when the class is loaded.
                        From what I can understand, the latter example is if you need to modify a primitive type property of a nested class. Alef's example was to change the *whole* nested class from a binded, primitive type form field. The PropertyEditor is required to tell spring how to "cast" a form string (that may represent a driver id for instance) to a new class instance.

                        Alef, I still struggle to understand your last post. As I am using a SimpleFormController, i created a subclass from it and implemented the initBinder with my propertyEditors. I extended my controllers from it as you said but that initBinder get call over and over again. Is there any way to declare and registers those in the application context so they are registered only once per session and called wheever they are required? If so, could you provide some sample code/xml?
                        Best regards,
                        Uze


                        Thanks for the post, uze, much appreciated. Alef, I'm kind of in the same boat as uze, would you be so kind as to point out some sample code that does this? Then I can play around with it and see how things work. Thanks again for the reply!

                        -=j=-

                        Comment


                        • #13
                          This thread has been informative. However, I'm still a little confused about property editors. I've got a business object, Job, that is selected from the UI by ID. However, my property editor needs access to the database to resolve the ID to a Job object. I've configured the following bean in *-servlet.xml where the appConfig bean has all of the data access stuff in it.

                          Code:
                          Code:
                             <bean id="jobPropertyEditor" class="com.myapp.util.propertyEditors.JobEditor"> 
                                 <property name="appConfig"> 
                                   <ref bean="appConfig"/> 
                                </property> 
                             </bean>
                          In my controller I register my editor as such

                          Code:
                          Code:
                          protected void initBinder&#40;HttpServletRequest request, ServletRequestDataBinder binder&#41; 
                                  throws Exception &#123; 
                              binder.registerCustomEditor&#40;Job.class, "job", &#40;JobEditor&#41; getApplicationContext&#40;&#41;.getBean&#40; 
                                      "jobPropertyEditor"&#41;&#41;; 
                          &#125;
                          Is this the correct way to wire things up? It seems to work. Any guidance would be much appreciated.

                          - Justin
                          Justin this is *NOT* the correct way to wire things up and could intorduce some realy nasty bugs once you start having a lot of concurent requests to your controller.

                          You need to register a *new* property editor for every call to binder.registerCustomEditor. The code that you have is reusing the single property editor that you created in the applicaiton context. It's realy annoying but property editors are *NOT* thread safe.

                          2 ways to fix the problem are:

                          1) mark the "jobPropertyEditor" bean as a prototype i.e. singleton="false":

                          Code:
                             <bean id="jobPropertyEditor" 
                                      class="com.myapp.util.propertyEditors.JobEditor"
                                      singleton="false"> 
                                 <property name="appConfig"> 
                                   <ref bean="appConfig"/> 
                                </property> 
                             </bean>
                          2) create a new instance explicitly in initBinder:

                          Code:
                              protected void initBinder&#40;HttpServletRequest request, ServletRequestDataBinder binder&#41; 
                                      throws Exception &#123;
                                  JobEditor jobEditor = new JobEditor&#40;&#41; 
                                  jobEditor.setAppConfig&#40;appConfig&#41;;
                                  binder.registerCustomEditor&#40;Job.class, "job", jobEditor&#41;;
                              &#125;
                          Ollie

                          Comment


                          • #14
                            Hi, I'm too new to Spring and am in the same situation trying to resolve a similar issue.

                            I have an EditUser class that depends on these three other classes User, Member and Group. I've the form binded to EditUser class such as

                            <input type="text" name="edituser.user.name">
                            <input type="text" name="edituser.user.password">

                            When I made changes in form and tried to update them to database, I found that the changes aren't getting saved. If I add the properties from each class to EditUser, I can see the changes made from the form. I can then manually set the new values to the appropriate class and save them that way but I don't really want to do it this way and I don't think it's the proper way of doing it.

                            I've read the post about registering the classes with registerCustomEditor(). I don't understand how that works. Can you someone help me out?

                            AND ALSO:
                            How do validate multiselect and checkboxes and retrieve the selected values in the controller?

                            Thank you.

                            Comment


                            • #15
                              Ok, I don't know if its what you are looking for but here's what I have:

                              My model that is passed to the form looks like:

                              Code:
                              Class myModel&#123;
                              	private int objectId;
                              	private User engineer;
                              	....
                              	....
                              
                              	public User getEngineer&#40;&#41;&#123;
                              		return user;
                              	&#125;
                              
                              	public void setEngineer&#40;User user&#41;&#123;
                              		this.user=user;
                              	&#125;
                              	
                              	...
                              &#125;
                              the User class looks like:

                              Code:
                              public Class User&#123;
                              	private int userid=0;
                              	private String username="";
                              	private String firstname="";
                              	private String lastname="";
                              	private String dept="";
                              
                              	public User getById&#40;int userid&#41;&#123;
                              		//return a user object filled from the database
                              	&#125;
                              	...
                              	&#40;getters and setters&#41;
                              &#125;
                              The jsp code looks like:

                              Code:
                              <spring&#58;bind path="dataModel.engineer">
                              	<SELECT size="1" name='<c&#58;out value="$&#123;status.expression&#125;" />1'>
                              		<c&#58;forEach var="engineer" items="$&#123;engineers&#125;">
                              			<OPTION value='<c&#58;out value="$&#123;engineer.userid&#125;" />'
                              				<c&#58;if test="$&#123;dataModel.engineer.userid==engineer.userid&#125;">SELECTED</c&#58;if>>
                              			<c&#58;out value="$&#123;engineer.firstname&#125; $&#123;engineer.lastname&#125;" /> </OPTION>
                              		</c&#58;forEach>
                              	</SELECT>
                              </spring&#58;bind>
                              Note that it binds to a combobox which value is the userid property of a User object. So when the form is submitted, I want the engineer property (which is a class) to be set to a new User class(whom was selected). And I guess that what you want to do. Naturally Spring has no way to know how to convert a string (which all form fields are) to a User object. For that you need propertyEditors that spring is gonna call when binding the form fields to your command object.

                              Create a class who extends PropertyEditorSupport and add those two methods (mandatory):

                              Code:
                              public class UserEditor extends PropertyEditorSupport&#123;
                              	public void setAsText&#40;String userid&#41; throws NoSuchElementException &#123;
                              	  // text is the string from the form, the userid is the identifier in
                              	  // the database
                                 
                                 		User user=null;
                              		if&#40;userid!=null&&!userid.equals&#40;""&#41;&#41;&#123;
                              		  	user=User.getById&#40;Integer.parseInt&#40;userid&#41;&#41;;   	 
                              		&#125; 	  
                              		setValue&#40;user&#41;;
                              	  
                              	&#125;
                              
                              	public String getAsText&#40;Object value&#41; &#123;
                              	//return the id value of the object
                              	
                              	  User user = &#40;User&#41; value;
                              	  return String.valueOf&#40;user.getUserid&#40;&#41;&#41;;
                              	&#125;
                              	
                              &#125;
                              Now, on binding Spring will call this everytime it tries to cbind a String to a User object. One last thing to do is to "register" your editor with spring so that it knows it exist. You do that in the initBinder() event of your FormController:

                              Code:
                              protected void initBinder&#40;HttpServletRequest request, ServletRequestDataBinder binder&#41;
                              		throws javax.servlet.ServletException &#123;
                              		//register all your custom editors here	
                              		binder.registerCustomEditor&#40;User.class,new UserEditor&#40;&#41;&#41;;
                              		
                              		SimpleDateFormat dateFormat = new SimpleDateFormat&#40;"d-MMM-yyyy h&#58;mm&#58;ss a"&#41;;
                              		dateFormat.setLenient&#40;true&#41;;
                              		binder.registerCustomEditor&#40;Date.class, null, new CustomDateEditor&#40;dateFormat, true&#41;&#41;;
                              	&#125;
                              Now post your form and...tadaaa! User is updated!

                              Hope that solves your problem!

                              Regards,

                              Uze

                              Comment

                              Working...
                              X