Announcement Announcement Module
Collapse
No announcement yet.
Binding Custom Object to Collection Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Binding Custom Object to Collection

    I have read many posts on this subject however, I have yet to see a solution to the problem. How do I bind an indexed property of ArrayList type with a custom object that contains properties I need set? I have found most developers bind to a single property but, I need the flexiblity to set multiple properties on my model object.

    For example...

    I have a FormAction that contains a model object in flow scope; Address which looks like this:

    Code:
    public class Address {
    	private String address1;
    	private String address2;
    	private String city;
    	private String state;
    	private Integer zip;
    	private ArrayList<Resident> residents = new ArrayList<Resident>();
    ... All Getters & Setters here...
    The Resident class looks like this:
    Code:
    public class Resident {
    	private String firstName;
    	private String lastName;
    ... All Getters & Setters here...
    Application context (address.xml) looks like this:
    Code:
    	<bean id="addressFormAction"
    		class="org.springframework.webflow.action.FormAction">
    		<property name="formObjectName" value="address" />
    		<property name="formObjectClass"
    			value="com.briskoe.model.Address" />
    		<property name="formObjectScope" value="FLOW" />
    	</bean>
    My flow definition looks like this:
    Code:
    <flow start-state="showNewAddressForm">
    
    	<view-state id="showNewAddressForm" view="addressForm">
    		<entry-actions>
    			<action bean="addressFormAction" method="setupForm"/>
    		</entry-actions>
    		<transition to="confirmAddressChange" on="submit">
    			<action bean="addressFormAction" method="bind" />
    		</transition>
    	</view-state>
    
    	<view-state id="confirmAddressChange"
    		view="confirmAddressChangeForm">
    		<transition to="complete" on="submit" />
    	</view-state>
    
    	<end-state id="complete" view="thankYou" />
    
    	<import resource="address.xml"/>
    
    </flow>
    I am executing this flow through a test case which looks like this:
    public class AddressFlowTest extends AbstractXmlFlowExecutionTests {
    Code:
    	@Override
    	protected ExternalizedFlowDefinition getFlowDefinition() {
    		return new ExternalizedFlowDefinition("address-flow",
    				new ClassPathResource("com/briskoe/address-flow.xml"));
    	}
    
    	public void testAddressFlow() {
    		final ApplicationView appView = (ApplicationView) startFlow();
    		assertCurrentStateEquals("showNewAddressForm");
    
    		final HashMap formData = new HashMap();
    		formData.put("address1", "1 Infinite Loop");
    		formData.put("address2", "Suite 200");
    		formData.put("city", "Cupertino");
    		formData.put("state", "CA");
    		formData.put("zip", "90210");
    
    		formData.put("residents[0].firstName", "Apple");
    
    		signalEvent("submit", new ParameterMap(formData));
    
    		assertCurrentStateEquals("confirmAddressChange");
    
    		assertModelAttributeNotNull("address", appView);
    
    		final Address address = (Address)appView.getModel().get("address");
    		assertEquals(1, address.getResidents().size());
    	}
    }
    The test case fails on signalEvent("submit"...) complaining about an Index Out of Bounds, which I suspected was caused by the binding not occurring correctly:
    Code:
    org.springframework.webflow.ActionExecutionException: Exception thrown executing [AnnotatedAction@1c74f37 targetAction = org.springframework.webflow.action.FormAction@641e9a, attributes = map['method' -> 'bind']] in state 'showNewAddressForm' of flow 'address-flow' -- action execution properties where 'map['method' -> 'bind']'; nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'residents[0]' of bean class [com.briskoe.model.Address]: Index of out of bounds in property path 'residents[0]'; nested exception is java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    Caused by: org.springframework.beans.InvalidPropertyException: Invalid property 'residents[0]' of bean class [com.briskoe.model.Address]: Index of out of bounds in property path 'residents[0]'; nested exception is java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    Has anyone tried to accomplish what I am and if so how did you do it?
    Last edited by Keith Donald; Jul 16th, 2006, 06:05 PM.

  • #2
    Finally! I have a solution to this problem... I was inspired by this comment: http://maas-frensch.com/peter/2006/0...estdatabinder/ . So, leveraging the Common's Collections ListUtils class I am able to give spring the ability to generate ArrayList entries on-demand. When Spring instantiates my Address class which is my Form backed bean, it uses my no-arg constructor. Knowing that, I added this bit of code to the constructor:

    residents = ListUtils.lazyList(new ArrayList<Resident>(), FactoryUtils.instantiateFactory(Resident.class));

    Then I updated my field within the Address class to be a null reference:
    private List<Resident> residents;

    Wow! It actually works now if I do things like this in my unit-tests:
    Code:
    		final ApplicationView appView = (ApplicationView) startFlow();
    		assertCurrentStateEquals("showNewAddressForm");
    
    		final HashMap formData = new HashMap();
    		formData.put("address1", "1 Infinite Loop");
    		formData.put("address2", "Suite 200");
    		formData.put("city", "Cupertino");
    		formData.put("state", "CA");
    		formData.put("zip", "90210");
    
    		formData.put("residents[0].firstName", "Foo");
    		formData.put("residents[0].lastName", "Bar");
    
    		formData.put("residents[1].firstName", "Red");
    		formData.put("residents[1].lastName", "Talorway");
    
    		signalEvent("submit", new ParameterMap(formData));
    
    		assertCurrentStateEquals("confirmAddressChange");
    
    		assertModelAttributeNotNull("address", appView);
    
    		final Address address = (Address) appView.getModel().get("address");
    		assertEquals(2, address.getResidents().size());
    I hope this helps someone.
    Last edited by Keith Donald; Jul 16th, 2006, 06:05 PM.

    Comment


    • #3
      Thanks for the solution. It works fine for the collections. But how do we handle the custom objects inside the collections. For example
      I have a form where I am collecting dependents for an insurer. Say there could zero to 10 dependents. So I have 10 optional field groups on my form each field group corresponding to one dependent entry

      Code:
      I have a collection person objects. 
      Person 
      - Name 
         - First Name
         - Last Name 
      - Age 
      
      In my velocity template file I would refer to them as 
      #springFormInput("Insured.dependent[0].name" "class=dis maxlength=128")
      #springFormInput("Insured.dependent[0].age" "")
      
      I have custom property editor to convert user input to Name object. And my form backing object has a collection of dependents (person objects).

      I can lazy initialize persons ArrayList but for each person instantiated I would have to pre-initialize the Name instance in its constructor. Is there a better solution for this?

      I have seen another thread
      http://forum.springframework.org/showthread.php?t=22015

      where it is mentioned to use


      Code:
      binder.setIgnoreInvalidFields(true); 
      
      and test for null in the view like 
      
      <c:if test="${target.address eq null}"><input name="address.city" value="" /></c:if>
      <c:if test="${target.address ne null}"><form:input path="address.city"/></c:if>

      I feel that its kind of ugly to have bunch of null checks in the JSP/vm file to solve this issue. Any good solution for this issue?

      Comment


      • #4
        it work like a charm

        Originally posted by jbriscoe View Post
        I hope this helps someone.
        Thanks a lot for the solution, I had like a day trying to figure it out why. until I found your post.

        thnx!

        Comment

        Working...
        X