Using Struts' nested indexed properties in a form with table-layout

47

It looks like trying to use Struts’ nested indexed properties in a form with table-layout, with every line (a few formfields) as an editable separate entity, has driven a lot of people (including me) nuts.
With nothing about it in the manual, and no answers to be found on the web, here’s finally a solution.

The JSP:

    <logic:iterate name="orderForm" property="orders" id="rememberMe">
        <html:text name="rememberMe" property="fieldA" indexed="true"/>
    </logic:iterate>

will create a list in HTML like

    <input type="text" name="rememberMe[ 0 ].fieldA">
    <input type="text" name="rememberMe[ 1 ].fieldA">

([0] and [1] being the index, which is added automatically (the ‘indexed=”true”‘ attribute)

The formbean of course needs the collection (assuming there is a Line class/mini-formbean, having fieldA as membervariable with a public getter):

    private List orderLines;
    public List getOrderLines() { return orderLines; }
    public void setOrderLines(List orderLines) { this.orderLines = orderLines; }

but also, here is the trick, a method with a special name that returns an indexed
element (orderline) from the collection of orders:

    public Line getRememberMe(int index) { return (Line)orderLines.get(index); }

As you see, the name of the method needs to correspond with the ‘id’ attribute in the
logic:iterate tag and the ‘name’ attribute in the html:text tag.

This looks like it’s going to work smoothly, but there’s still one problem left.
Everytime a client posts a new request he gets a new (resetted) formbean, so the
collection is not initialized at the moment the form-values will be copied into the bean.
Normally this is ok, but since Struts (or commons.BeanUtils, actually) calls the
“getRememberMe” method to get an element from the collection to set a value in one of
its fields, this can result in a NullPointerException or ArrayIndexOutOfBoundsException.

There are of course multiple solutions for this, among which:

  • If you know the maximum amount of orderlines you will have in your form, you could pre-construct
    them all in the constructor of the formbean, using default values.
  • An automatically growing collection that creates, initializes and adds a new element to
    the collection if the getter is called. This might feel weird at first, but i do think
    it is the best solution. It has the additional plus that a dynamically growing form
    (for example by using the javascript/DOM cloneNode method) will always fit in the collection.

This class could look something like this:

public class AutoGrowingList extends ArrayList
{
    private Class clazz;

    public AutoGrowingList(Class clazz)
    {
        super();
        this.clazz = clazz;
    }

    public Object get(int index)
    {
        Object obj = super.get(index);
        if (obj == null)
        {
            obj = clazz.newInstance();
            super.add(obj);
        }
        return obj;
    }
}
Share.

About Author

47 Comments

  1. I would be thanked, if you post an example with a collection inside other collection, with indexed=true.
    My problem it’s when i fill the text inputs, then submit form and some error occurs, struts can’t populate my collections… :| (yes my lists are instanciated)

  2. When i search for this issue, i found this. Eventhough it is a old one, as i find the solution in another way, i want to present it.

    Consider a bean named PersonVO. Now you have an array of PersonVO objects.

    Set it to the form as create a instance variable PersonVO[] arrPerson = new PersonVO[10]. Initialize with default size or follow the steps that presented in the forum earlier.

    In jsp page, when you iterate this, use the id name same as the name declared in the form.

    Now if you change the first name, the in action class if you get the arrPersons object, you will get the updated one.

  3. There is a comment wrote by Stephen Montgomery on comment-6971 that says..

    “I have a collection in collection situations. Where my form bean has a collection of objects (deosetBeans, here) and they have an attribute which itself is a collection (deoBeans, here); which I wanted to use in a form. I implemented the get/set methods as above in both the bean classes for the objects in the collections and then ran the JSP like this:
    All was gravy”

    I have exactly the same problem as mentioned on there, and I can’t resolve the problem, if someone has the answer please let me know, or tell me where can I read more about it.

    I will appreciate any help..

  4. I just noticed that the html tags were swallowed up.

    In my earlier post, i meant that the HTML:IMAGE tag does not support the ‘name’ attribute as the HTM:TEXT tag .

  5. How do i resolve a similar problem ….Along with the indexed text fields i also have to generate a Delete button (ImageButtonBean) for each row. The tag does not support the ‘name’ attribute as the . So, I am stuck with the ArrayIndexOutofBoundException where my buttons are concerned.

    I am at a loss here as to how to resolve this. Does someone have an answer for this?

  6. This is more related to the above mentioned problem.

    How do we proceed if we have a form with two different collections under the same form. Both the collection elements need to be mapped to the form elements on the UI, they should be editable and the list can grow as well.

  7. I’m having problem with setter methods.
    The html contains something like

    <input type=”text” name=”order[0].name” value=””>
    <input type=”text” name=”order[1].name” value=””>

    I have defined both

    Order getOrder(int index)

    and

    void setOrder(int index, Order order)

    methods in my form bean, but unfortunately only the getter is called, the setter does not ever get called, so the submitted indexed values are lost. I suppose I can set them via request.getParameter(), but isn’t there any other way? I’m using Struts 1.2.9

  8. public class AutoGrowingList extends ArrayList
    {
    private Class clazz;

    public AutoGrowingList(Class clazz)
    {
    super();
    this.clazz = clazz;
    }

    public Object get(int index)
    {
    int size = this.size();
    if (index >= size)
    {
    for (int i = 0; i

  9. Kindly pls get me the Code so that i can work. I am having problem in setting values.

  10. Wonderful! It has taken me all day to understand why i was getting a null pointer exception, like you say nothing is documented – not even on the oft-mentioned “struts”/faq/indexprops.html page. Now at last i fully understand

    Thank you for making this public.

  11. All the posts on this page (including the original) make a dangerous assumption, which is the web browser will return all the Line objects in the order that Struts wrote them to the JSP. If it does not, at best the objects will be out of order, and at worst List objects will overwrite each other and data will go missing. Isn’t preserving order the whole point of indexing?

    example problem:
    rememberMe[1] is put into the Request first, followed by rememberMe[0].

    In this case, rememberMe[1] flows through List.add() because its index (1) is NOT in the list (size zero). It is followed by rememberMe[0] which is retrieved, not added, because its index (0) IS now in the list (size 1). Unfortunately rememberMe[1] is retrieved and overwritten with the contents of rememberMe[0].

    the AutoGrowingList.get() function in the original post should be modified to:

    public Object get(int index) {
    if(index >= super.size()) {
    for(int i=0; i

  12. Just to be clear, the above method requires care around the reuse of collections across requests because of the single default object – the below implementation is a little less error prone:

    public interface InstanceFactory {
    /** Get an instance of the class this generates */
    T getInstance();
    }

    public final class AutoGrowingList extends java.util.ArrayList {

    /** Because ArrayList is serializable. */
    private static final long serialVersionUID = 3256722870804559412L;

    /** The object that will be used to fill out the list if necessary */
    private final InstanceFactory instanceFactory;

    /**
    * Sole constructor.
    *
    * @param defaultObject the object that will fill out the list if necessary
    */
    public AutoGrowingList(final InstanceFactory instanceFactory) {
    this.instanceFactory = instanceFactory;
    }

    public final T get(final int index) {
    while (this.size()

  13. This is a great solution to the problem of entering an unlimited number of elements in a struts form, which I am using on my site. However, if you are using java 5, the following genericised version is type safe, and can’t throw instantiaion errors or access errors if used carelessly. I hope this is of use to people:

    import java.util.ArrayList;

    /**
    * An ArrayList that grows automatically, so that it never throws an
    * ArrayIndexOutOfBoundsException. In the case where the list needs to grow,
    * it will add the default object passed in to the constructor until the list is the
    * required size.
    *
    * @author Mark Slater
    */
    public final class AutoGrowingList extends ArrayList {

    /** Because ArrayList is serializable. */
    private static final long serialVersionUID = 3256722870804559412L;

    /** The object that will be used to fill out the list if necessary */
    private final T defaultObject;

    /**
    * Sole constructor.
    *
    * @param defaultObject the object that will fill out the list if necessary
    */
    public AutoGrowingList(final T defaultObject) {
    this.defaultObject = defaultObject;
    }

    public final T get(final int index) {
    while (this.size()

  14. Iam confused with this code



    Author is using property=”orders” in the above code..And using the following code. where he referss
    it as orderLines

    private List orderLines;
    public List getOrderLines() { return orderLines; }
    public void setOrderLines(List orderLines) { this.orderLines = orderLines; }

    If some one understood it properly please help me..

    Thanks In Advance

    Priya

  15. Hi,

    It is a very good example. It really helped me in solving the problem.
    Make sure you have getxxxxxxx(int index) implemented properly. Otherwise when form is submitted you may get errors.
    The above mentioned one “getCurrUnitAsgnRule(int index)” example solved my problem.

  16. How do you make this work with nested c:forEach loops? My inner loop has correctly added [n] to each bean, but it doesn’t account for the outer loop variables. I have 4 levels of nested loops.
    Thanks and good post.
    Steve

  17. Hi..
    Its excellent. But I am facing a small problem.
    The collection is getting submitted, but some of the attributes of objects in collection
    are coming null !!! Has anybody faced the same problem. Please guide !!!
    Here are some results

    RecIndex :0
    Status :-1 Sub To Client : clientdate0 Interview Date : interdate0 Date Started : startDate0
    Status :-1 Sub To Client : clientdate1 Interview Date : interdate1 Date Started : startDate1
    Status :-1 Sub To Client : clientdate2 Interview Date : interdate2 Date Started : startDate2
    Status :-1 Sub To Client : clientdate3 Interview Date : interdate3 Date Started : startDate3
    Status :-1 Sub To Client : null Interview Date : interdate4 Date Started : startDate4
    Status :-1 Sub To Client : clientdate5 Interview Date : interdate5 Date Started : startDate5
    Status :-1 Sub To Client : clientdate6 Interview Date : null Date Started : startDate6
    Status :null Sub To Client : clientdate7 Interview Date : interdate7 Date Started : startDate7
    Status :null Sub To Client : clientdate8 Interview Date : interdate8 Date Started : startDate8
    Status :null Sub To Client : clientdate9 Interview Date : interdate9 Date Started : startDate9

    Rahul

  18. Hi..
    Its excellent. But I am facing a small problem.
    The collection is getting submitted, but some of the attributes of objects in collection
    are coming null !!! Has anybody faced the same problem. Please guide !!!

    Rahul

  19. Abu Muinuddin on

    Excellent! I was unable to resolve this nested indexing issues for long time. This article really helped me

    //here’s the form Bean
    public Collection getUnitsAssignmentsRules() {
    return unitsAssignmentsRules;
    }

    /**
    * @param collection
    */
    public void setUnitsAssignmentsRules(Collection collection) {
    unitsAssignmentsRules = collection;
    }

    //UnitAssignmentRule is bean which represents one record on screen
    //Getter Method
    public UnitAssignmentRule getCurrUnitAsgnRule(int index){
    while(index >= unitsAssignmentsRules.size()) {
    unitsAssignmentsRules.add(new UnitAssignmentRule());
    }
    return (UnitAssignmentRule)((ArrayList) unitsAssignmentsRules).get(index);
    }

    You don’t need the setter method for each collection. you can set the collection as you retrieve from db.

    JSP implementation –



  20. I have a collection in collection situations. Where my form bean has a collection of objects (deosetBeans, here) and they have an attribute which itself is a collection (deoBeans, here); which I wanted to use in a form. I implemented the get/set methods as above in both the bean classes for the objects in the collections and then ran the JSP like this:
    All was gravy

    td>
    property='<%= "deosetBean[" + i + "].deoBean[" + j + "].count" %>'
    size="10"
    maxlength="10"/>

    table>


  21. I feel like i lost about three days of my life trying to sort this problem out! I’d got to the ‘NullPointerException’ step but couldn’t work out what I needed to do next! Needless to say your solution has helped me to reclaim my sanity.

    Many many thanks!

  22. If I submit the form will I have chnaged values in my nested bean inside the arraylist though?

  23. Leon van Tegelen on

    If you sent us the code (weblog@amis.nl) we will put it in a seperate post under your name if you like

  24. Can’t post the HTML and jsp sourch
    //JSP
    l o g i c : iterate id=”lineRecord” name=”MyActionForm” property=”listRecords” type=”MyBean”
    h t m l : text indexed=”true” name=”lineRecord” property=”propOne”

  25. Oops … This forum doesn’t allow post of html / jsp code ! Trying again. sorry for above.
    //JSP

    < logic : iterate id="lineRecord"
    name="MyActionForm"
    property="listRecords"
    type="MyBean">

    ….
    //HTML
    ….
    < input type="text" name="lineRecord[0].propOne"/ >
    < input type="text" name="lineRecord[1].propOne"/ >
    < input type="text" name="lineRecord[2].propOne"/ >
    ….

  26. This post was usefull to me. I have merged all and giving the working set of code for using
    dynamic, indexed and nested property with struts. I hope you will find this usefull.

    //jakarta-struts-1.2.4
    //MyActionForm.java

    private List listRecords;//dynamic, nested and indexed property

    public SurchargeDiscountActionForm() //Constructor {
    listRecords = new ArrayList();
    }
    //MyBean is bean which represents one record on screen
    //Getter Method
    public MyBean getLineRecord(int index) {
    while(index >= listRecords.size()) {
    listRecords.add(new MyBean());
    }
    return (MyBean) listRecords.get(index);
    }

    //Setter Method
    public void setLineRecord(int index, MyBean object) {
    if (index listRecords.set(index, object);
    } else {
    listRecords.add(object);
    }
    }
    ....
    //JSP
    ...
    name="MyActionForm"
    property="listRecords"
    type="MyBean">

    ….
    //HTML
    ….



    ….

  27. Here is the form i crated using a LazyList

    in Action Form :
    protected List deviceList = LazyList.decorate(new ArrayList(), this);
    .
    .
    .
    /**
    * @return Returns the deviceList.
    */
    public List getDeviceList() {
    return deviceList;
    }

    public void setDeviceList(List list) {
    this.deviceList = list;
    }

    public LabelValueBean getDeviceList(int index){
    return (LabelValueBean)deviceList.get(index);
    }

    /* (non-Javadoc)
    * @see org.apache.commons.collections.Factory#create()
    */
    public Object create() {
    return new LabelValueBean(“”,””);
    }

    public void addDevice() {
    deviceList.add(new LabelValueBean(“”,””));
    }

    in JSP:

    Ahh okay. That would definitely throw an error.

    Of course, if you did a database look up and it returned no results, you may want to redirect back to the search page to
    change the search. Also, if the list is empty, do you really want to create a new item and send it to the user?

    An alternative for the jsp page, you can check to see if the collection is empty the user. Lots o’choices =)
    perogi.

  28. The original list is empty. Stub code, no database access right now, just getting Struts
    to display/add/delete from a list.

  29. Really weird Alok. I will need to check and post my exact code that I am using. One question, how are you filling the
    original List? From a database look-up?
    perogi

  30. Update,

    I tried the code above and it it still gives me an ArrayIndexOutfBound.. It looks like
    the get(index) method on the arraylist will throw that if the list is empty.

    Here’s what I do..

    public Item getKey(int index) {
    if (keys.isEmpty()) {
    keys.add(new Item(“”));
    return (Item) keys.get(0);
    }
    Object o = keys.get(index);
    if (o == null) {
    o = new Item(“”);
    keys.add(o);
    }
    return (Item) o;
    }

    public void setKey(int index, Item line) {
    if (index < keys.size()) {
    keys.set(index, line);
    } else {
    keys.add(line);
    }
    }

    public class Item {
    public String getTest() { ..}
    public void setTest(String s) { .. }
    }

    --------------

  31. Howdy! I actually use this in my struts app at my current client. In order to do the ‘setting’ just use a method with this
    sig.


    public void setRememberMe(int index, Line line) { // note well, the code is at my client, it could be (Line line, int index)
    if (index < orderLines.size() ) {
    orderLines.set(index, line);
    } else {
    orderLines.add(line);
    }
    }

    Also, typically I would use this type of naming convention.




    n change the methods to

    getOrder(int index) and setOrder(Object obj, int index);

    Hope this helps!
    perogi.
    p.s. I also created a tag for the (previously) very depressing problem of





    ould not append an additional [index] so the struts Form could figure out which text input to use. Solved baby =)
    I will post this code very soon.

  32. Nice work. This Struts quirk wasted a lot of my time. You should send this to Jakarta have this
    posted on their site.

  33. Leon van Tegelen on

    Good stuff!
    An alternative, less OO,less general, a bit more code in the processing Action,
    straightforward, commonly used alternative in this case would be not to use the indexed orderLines, but arrays or
    lists of each of the individual attributes (indexed). That approach leads to the messy JSP pages with
    property='< %= "field1["+index+"]" %> code in the html text tags .
    I prefer your method! Clean …