Menu

multiple command objects on the same form ?

Anonymous
2004-06-23
2004-06-25
  • Anonymous

    Anonymous - 2004-06-23

    Hello,

    I spent several hours searching this forum, after several days studying my problem, and couldn't find a solution...
    Please help me !
    Let me explain it by a simple example :

    public class Contact {
        private String id = "z";
        private boolean member;
        private Set addresses = new HashSet();
       
         public Set getAddresses() {
            return addresses;
        }
       
        public void setAddresses(Set addresses) {
            this.addresses = addresses;
        }
    ...
    }

    public class Address {
        private String addressId = "z";
        private String zipCode;
        private String city;
        private Contact contact;
    ...
    }

    I want users to fill a form with their names, address etc.(yes, in my example, a contact may have several addresses !).
    Thus I have to bind 2 command objects (one for Contact, and one for Address)to the form, within the same SimpleFormController.
    How can I do that ? (Since I'm a newbie, please give me a detailed answer ;-)
    Thanks a lot for your help.

    Lazar

     
    • Seth Ladd

      Seth Ladd - 2004-06-23

      This isn't really multiple command objects on the same form.  You still have only one command object: Contact.  You will only <spring:bind> to that.

      Now, as you show, a command object is just a bean.  A bean can have references to other beans.  In this case, a Contact can have many Addresses.

      Spring allows you to bind to child objects, with few caveats.  That means you can access child objects of the command object through the <spring:bind> tag.

      For example:

      <spring:bind name="address[0].city" />

      This would pull the value from city from the first Address in a List or Array.

      I don't know, however, if you can bind to a Set.  You can bind to a List, Array, or Map quite easily, though.

      If you changed your above Contact to:

      public List getAddresses()

      Then you can use the bind tag to access them as shown above.

      One more thing: you must initialize the objects in the collection when initializing the command object itself.

      In the constructor, do something like this:

      public Contact() {
        for (int i = 0; i < 10; i++) {
          addresses.add(new Address());
        }
      }

      This is because the data binding process can't call setCity() on a null Address.

       
      • Matt Raible

        Matt Raible - 2004-06-24

        <quote>
        public Contact() {
        for (int i = 0; i < 10; i++) {
        addresses.add(new Address());
        }
        }
        </quote>

        Is there an easier way.  In Struts, you can use use a LazyList in your reset() method to populate a list dynamically with empty objects.  You can see the code at the link below (since pasting code in these forums sucks):

        http://tinyurl.com/2ckmv

         
    • Anonymous

      Anonymous - 2004-06-24

      Thanks a lot for your quick, efficient and detailed answers, guys ! It's so nice from you to have spent time to explain these things to me :-)

      Matt : your solution would be cool, but when I started my project, I was looking for a framework which could hold all my needs, in order to avoid to learn and mix dozens of different technologies, that's why I choosed Spring with Hibernate, and I don't want to mix it with Struts (I've got enough work to learn Spring !).

      Seth : your answer perfectly fit my needs. You made my day ! I think I would have never found without your help. Now I'm just wandering how will I make the same thing when it comes to a collection that I don't want indexed, like a Set, or a form with 2 completely distinct objects, but I will surely post again then...

      Best regards for you both

      Lazar

         

       
      • Matt Raible

        Matt Raible - 2004-06-25

        Lazar - I just wanted to say that I wasn't advocating that you use Struts.  I was trying to indicate a need for Spring MVC to have a similar mechanism.  Maybe it's possible to use an interceptor that figures out how many indexed properties there are, inspects the command object, and then instantiates a bunch of empty objects.  While the constructor thing works, it seems to be more of a workaround than a clean solution.  BTW, I'm not saying that Struts has a clean solution, but it's the one used by most of the community.

         
      • Seth Ladd

        Seth Ladd - 2004-06-25

        I'm not sure you can support a Set.  A List or Map I know you can do (and a little more logically than Struts, IMHO but the same idea).

         
    • Anonymous

      Anonymous - 2004-06-25

      I've actually developed a really cool solution since one of the things I don't like about Spring mvc is the way it does form beans.  So I just rewrote it.

      I first created a generic bean that's contains a map, implements map and provides ways to get/set the values and do the property editing of those values.  It can look up the values based on an expression language.  I've been able to implement it using jxpath and others, so you can just plug in the one you want to use and be done with it.

      Now, in order for the map to be really useful, you want to do dynamic and physical object lookups.  For example, I might want to go contact.name, and that will look up "contact" in the map and do getName() on that object.  I might also want to just get the dynamic property "contact.name".  A good way to implement the form bean would be to automatically known which one I'm talking about (which can be done without specifying it in metadata either).

      Next, you wanna specify some controls, so that way the framework can know how to bind and map those controls back to the physical objects.  For example, spring does the basic property editing just fine, but how about maping an array of longs to your reference data so that you can populate a collection on the command object?  So, you want to specify a whole range of control types, like the ones spring has (call them DateControl, StringControl, IntegerControl), but also add new ones like SelectOneControl or SelectManyControl.  You'll also no doubtedly see possibilities where you want to escape html or just quotes, so you can make TextControl, HtmlControl and RichTextControl.

      Now the best part is that these controls are mapped to html elements, so you just specify the expression reference (dynamic or physical), the type and so on.  You can just make an xml configuration file for this and create a factory to populate the controls metadata with it.  Here is the one I use (just an example).

          <form controller="com.company.project.web.LoginFormController">
              <control name="user.username" type="base:text" check="method:emptystring" />
              <control name="user.password" type="base:text" check="method:password" error-message="passwords must be at least 4 characters." />
              <control name="user.automaticLogin" type="base:boolean" />
          </form>

      Here, the first two are actually properties of a UserAccount object in my case.  The third is a dynamic property that is actually a checkbox.  The beauty about having this kind of metadata is that implementing checkboxs is very easy.  Note too that you could just use an html select instead of a checkbox since all you want to do is specify that it's boolean.  The html element that implements the control should not be known.

      Also notice that put validation constraints in there too, just because some applications I write don't need validation at the other levels, but you can leave this out if validation needs to be done at any layer of the application (the database, business services, etc.)

      You can even do something similar for wizards.  This one demonstates a lot of the functionality you could build into such a tool.

          <!-- Pages -->
          <form controller="com.company.project.web.PageFormWizardController">
              <page index="0" view-bean="pageFormView">
                  <control name="page.parent" type="base:selectone" reference-data="pages" null="true" />
                  <control name="page.name" type="base:text" check="method:emptystring" />
                  <control name="page.headerName" type="base:text" check="method:emptystring" />
                  <control name="page.title" type="base:text" check="method:emptystring" />
                  <control name="page.abstraction" type="base:selectone" reference-data="abstractions" null="true"  />
                  <control name="page.language" type="base:selectone" check="method:reference" reference-data="languages" />
                  <control name="page.owner" type="base:selectone" check="method:reference" reference-data="users" />
                  <control name="page.view" type="base:selectone" check="method:reference" reference-data="views" />
                  <control name="page.isPublished" type="base:boolean" />
                  <control name="page.allowComments" type="base:boolean" />
                  <control name="page.hideOnSitemap" type="base:boolean" />
              </page>
              <page index="1" view-bean="pageContentFormView">
                  <control name="page.content" type="base:richtext" check="method:emptystring" />
                  <control name="page.keywords" type="base:text" />
                  <control name="fileToUpload" type="base:file" />
              </page>
              <page index="2" view-bean="pageSecuritySettingsFormView">
                  <control name="page.useParentSecuritySettings" type="base:boolean" />
                  <control name="page.allowedRoles" type="base:selectmany" reference-data="userRoles" />
                  <control name="page.allowedGroups" type="base:selectmany" reference-data="userGroups" />
              </page>
              <page index="3" view-bean="pageMenuItemsFormView" />
          </form>

      Not that selectone and selectmany controls just map themselves to reference data.  Since it can use the expression language to know which object it should map, the object will be mapped automatically for you just before you process the form.

      I didn't implement this solution.  A fellow named Ken Egervari did and it has made form development very, very easy.  Form controllers typically become extremely small, as all the mapping is stripped out.  It's like hibernate but it's for html to domain objects.

      For example, here is some of the code that goes in the controller:

      to create the form bean:
              Page page = new Page();
              page.setIsPublished( Boolean.TRUE );
              page.setParent( cms.getWebsiteResource().findPage( getIdParameter( request, "parentId" ) ) );

              form.put( "page", page );

      Note that many of the controls were not set.  The framework can set them to default values (if they are physical or dynamic).  If you want to override the default value, you can just set it here.

      loading a form bean:
              Page page = pageServices.findPopulatedPageForEdit( getIdParameter( request, "pageId" ) );

              form.put( "page", page );

      on submit:

              Page page = (Page) form.get( "page" );
              WebFile file = (WebFile) form.get( "fileToUpload" );

              if( !file.isEmpty() ) {
                  page.setContent( file.getAsString() );
              }

              if( page.getAbstraction() == null ) {
                  page.createNewAbstraction();
              }

              pageServices.storePage( page, wasViewChanged( page, form ) );

      Anyway, this gives you something to aim for.  If a lot of people are interested in such an idea, maybe I can bug him to open the source up.  It's 100% spring compatible (it's built on spring).

       
      • Anonymous

        Anonymous - 2004-06-25

        Oh, the whole point for saying this is that you could form.put( "command1", new Command1() ); and form.put( "command2", new Command2() ) by doing it this way.  You could also implement arrays and all kinds of funky stuff using this technique, making it very flexible.

         

Log in to post a comment.

MongoDB Logo MongoDB