JavaRanch Home    
 
This page:         last edited 17 April 2007         What's Changed?         Edit

Indexed Properties   

I have an ArrayList of JavaBeans in my ActionForm and would like my JSP to display a table that has input fields for each bean in the list. How do I do that in Struts?

In order to do this, you must use "indexed properties". This term refers to Struts' ability to use a property name that has an index attached to it. It does take a bit of effort to learn how to use indexed properties, but once you get the hang of it, indexed properties can be a very useful tool.

You should start by reading these links:

One of our frequent JavaRanch contributors, Brent Sterling, has also written a simple example of using indexed properties. His code demonstrates the use of "lazy intitialization" in the indexed getter so that the ActionForm can be placed in request scope without causing an "index out of bounds" exception. The code and his comments are included below.



In the following example, the page is a simplified shopping cart where the user sees a list of items in their shopping cart and can edit the quantities of each item.

entries from struts-config.xml


<form-bean name="OrderIndexedForm" type="my.examples.indexed.OrderIndexedForm" />
 
<action
        path="/DisplayOrderIndexed"
        type="my.examples.indexed.DisplayOrderIndexedAction"
        name="OrderIndexedForm"
        validate="false"
        scope="request">
    <forward name="success" path="/WEB-INF/jsp/indexed/order_indexed.jsp"/>
</action>
<action
        path="/SaveOrderIndexed"
        type="my.examples.indexed.SaveOrderIndexedAction"
        name="OrderIndexedForm"
        validate="true"
        input="/DisplayOrderIndexed.do"
        scope="request">
    <forward name="success" path="/DisplayOrderIndexed.do" redirect="true"/>
</action>


OrderIndexedForm: the ActionForm


public class OrderIndexedForm extends ActionForm
{
 
    private List orderList;  // list of OrderItem objects
 
    public void setOrderList(List orderList)
    {
        this.orderList = orderList;
    }
 
    public List getOrderList()
    {
        return this.orderList;
    }
 
    // this is the method that will be called to save
    //  the indexed properties when the form is saved
    public OrderItem getOrderItem(int index)
    {
        // make sure that orderList is not null
        if(this.orderList == null)
        {
            this.orderList = new ArrayList();
        }
 
        // indexes do not come in order, populate empty spots
        while(index >= this.orderList.size())
        {
            this.orderList.add(new OrderItem());
        }
 
        // return the requested item
        return (OrderItem) orderList.get(index);
    }
 
}


OrderItem: a simple data bean


public class OrderItem
{
    private String productId;
    private String productName;
    private String quantity;
 
    <constructors plus get and set methods here>
}



DisplayOrderIndexedAction: action that shows the page


public class DisplayOrderIndexedAction extends Action
{
 
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception
    {
        OrderIndexedForm indexedForm = (OrderIndexedForm) form;
 
        // get the list of orders from our business layer
        List orderList = IndexedBusinessLayer.getOrders();
 
        // save the list of order on our form
        indexedForm.setOrderList(orderList);
 
        return mapping.findForward("success");
    }
 
}


order_indexed.jsp: the form section


<html:form action="/SaveOrderIndexed" >
    <table width="100%" border="1">
        <tr>
            <th width="25%" >Product ID</th>
            <th width="60%" >Name</th>
            <th width="15%">Quantity</th>
        </tr>
        <logic:iterate id="orderItem" name="OrderIndexedForm" property="orderList">
            <html:hidden name="orderItem" property="productId" indexed="true" />
            <tr>
                <td><bean:write name="orderItem" property="productId" /></td>
                <td><bean:write name="orderItem" property="productName" /></td>
                <td><html:text name="orderItem" property="quantity" size="30" indexed="true" /></td>
            </tr>
        </logic:iterate>
    </table>
    <html:submit>Save</html:submit>
</html:form>



SaveOrderIndexedAction: the action that saves the updated quantities


public class SaveOrderIndexedAction  extends Action
{
 
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response) throws Exception
    {
        OrderIndexedForm indexedForm = (OrderIndexedForm) form;
 
        // get the list of order from the form
        List orderList = indexedForm.getOrderList();
 
        // update the ordered quantities using the business layer
        IndexedBusinessLayer.updateOrderQty(orderList);
 
        return mapping.findForward("success");
    }
 
}

Two tips that I have learned that seem to cause problems are:

Use a different name for your indexed get method

In my example I had a list of "order" items. For the get method that returns a List I chose the name getOrderList. For my indexed get method I chose the name getOrderItem. Some example use a name like "getOrders" for both methods, but this causes problems with some versions of Struts/BeanUtils, plus it seems more confusing to me. I also see examples that use names like getOrders and getOrder but to me the difference is subtle enough to cause confusion.

Note that the method setOrderList is never called by Struts. It is only called from DisplayOrderIndexedAction.

Make sure the id attribute of your iterate tag matches the property name of your indexed get method

Note that the id attribute of my iterate tag is "orderItem" and my indexed get method is named "getOrderItem". If they do not match, then your page will display fine but your form will not be populated when the page is submitted.

It took me a while to figure this one out. For the first two years after I found out about indexed properties I did not use the indexed="true" attribute. Instead I used JavaScript to create the index like in the "Dynamic Indexes for Indexed Properties" section of the Struts document.

Note that my example completely avoided the topic of validation. I have always implemented validation for indexed properties by implementing a validate method in the form (plus I never use client side validation). My understanding is that the validator framework does not support indexed properties.

I also did not do any translation between "business/data objects" and "presentation objects". Struts wants String properties for editable fields. Note that the quantity property in OrderItem is a String. In reality your business layer would return an object where quantity was an int or Integer (or long/Long) and the code would have to do some translation. This translation would occur in reverse when the quantity was updated.

For completeness, here is my dummy/mock business layer object:


public class IndexedBusinessLayer
{
 
    // in reality this data should come from a database, here it is just in memory
    private static Map orderMap = new HashMap();
 
    static
    {
        orderMap.put("493-LW"new OrderItem("493-LW""Coke, 48oz""6"));
        orderMap.put("693-CL"new OrderItem("693-CL""Granola Snack, 1 lb""3"));
        orderMap.put("145-JD"new OrderItem("145-JD""Pretzel Sticks, 130 oz""1"));
        orderMap.put("247-WQ"new OrderItem("247-WQ""Popcorn with Butter, Super Large""6"));
        orderMap.put("789-MB"new OrderItem("789-MB""Mike & Ike, 3 lbs""2"));
    }
 
    public static List getOrders()
    {
        return new ArrayList(orderMap.values());
    }
 
    public static void updateOrderQty(List orderList)
    {
        for(Iterator orderIter = orderList.iterator(); orderIter.hasNext() ;) 
        {
            OrderItem newItem = (OrderItem) orderIter.next();
            String key = newItem.getProductId();
            OrderItem orderItem = (OrderItem) orderMap.get(key);
            if(orderItem != null)
            {
                orderItem.setQuantity(newItem.getQuantity());
            }
        }
    }
}



Return to StrutsFaq

JavaRanchContact us — Copyright © 1998-2014 Paul Wheaton