September 17, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Succeeding With Struts: Indexed Properties and Beans as Properties

  • July 10, 2003
  • By James M. Turner
  • Send Email »
  • More Articles »

In last month's article we looked at how to use DynaForms to replace standard form beans with dynamically created ones.  This month we can take it one step further and examine how to implement master-detail records, something that occurs frequently in web applications.

For example, suppose you're implementing a purchase-order form.  You have 4 rows, each one of which has multiple columns (part number, quantity, price, etc.)  Using a brute for approach, you could implement it like so:

struts-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC 
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" 
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
 <form-beans>
  <form-bean name="purchaseOrderForm" type="org.apache.struts.action.DynaActionForm">
    <form-property name="partNumber1" type="java.lang.String"/>
    <form-property name="quantity1" type="java.lang.String"/>
    <form-property name="price1" type="java.lang.String"/>
    <form-property name="partNumber2" type="java.lang.String"/>
    <form-property name="quantity2" type="java.lang.String"/>
    <form-property name="price2" type="java.lang.String"/>
    <form-property name="partNumber3" type="java.lang.String"/>
    <form-property name="quantity3" type="java.lang.String"/>
    <form-property name="price3" type="java.lang.String"/>
    <form-property name="partNumber4" type="java.lang.String"/>
    <form-property name="quantity4" type="java.lang.String"/>
    <form-property name="price4" type="java.lang.String"/>
  </form-bean>
 </form-beans>
 <action-mappings>
  <action path="/generatePO" type="article2.GeneratePO" 
          name="purchaseOrderForm" scope="request" input="/purchaseOrder.jsp">
      <forward name="success" path="/displayPurchaseOrder.jsp" redirect="false" />
  </action>
 </action-mappings>
 <message-resources parameter="ApplicationResources" />
 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
   <set-property value="/WEB-INF/validator-rules.xml" property="pathnames" />
 </plug-in>
</struts-config>

This would tie into a JSP page, which needs to explicitly call out each field:

purchaseOrder.jsp

<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<head>
<title>Example of a Brute Force Purchase Order</title>
</head>
<h1>Example of a Brute Force Purchase Order</h1>
<html:errors/>
<html:form action="/generatePO">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>
<TR><TD><html:text property="partNumber1"/></TD>
<TD><html:text property="quantity1"/></TD>
<TD><html:text property="price1"/></TD></TR>
<TR><TD><html:text property="partNumber2"/></TD>
<TD><html:text property="quantity2"/></TD>
<TD><html:text property="price2"/></TD></TR>
<TR><TD><html:text property="partNumber3"/></TD>
<TD><html:text property="quantity3"/></TD>
<TD><html:text property="price3"/></TD></TR>
<TR><TD><html:text property="partNumber4"/></TD>
<TD><html:text property="quantity4"/></TD>
<TD><html:text property="price4"/></TD></TR>
</table>
<html:submit/>
</html:form>

And, of course, the appropriate Action to process the form:

GeneratePO.java

package article2;

import org.apache.struts.action.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import article2.Utils;

public class GeneratePO extends Action {
   public ActionForward execute(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException{

      DynaActionForm poForm = (DynaActionForm) form;
      if (!Utils.nullOrBlank((String)poForm.get("partNumber1"))) {
          request.setAttribute("total1", computeTotal(poForm, "price1", "quantity1"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber2"))) {
          request.setAttribute("total2", computeTotal(poForm, "price2", "quantity2"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber3"))) {
          request.setAttribute("total3", computeTotal(poForm, "price3", "quantity3"));
      }
      if (!Utils.nullOrBlank((String)poForm.get("partNumber4"))) {
          request.setAttribute("total4", computeTotal(poForm, "price4", "quantity4"));
      }

      return mapping.findForward("success");
   }

   private Double computeTotal (DynaActionForm form, String price, String quantity) {
      return new Double(article2.Utils.parseFormFieldToInt(form, quantity) *
                        article2.Utils.parseFormFieldToDouble(form, price));

   }
}

This approach, however, is both inelegant and inflexible, in order to add another line you'd need to manually add three more form-properties, and change both your JSP form and Action to know about them.  Luckily, Struts 1.1 offers a convenient way to both deal with this type of form, and to expand it dynamically (or even at runtime) without having to change any code or JSP.

To begin, we define a simple Java Bean that holds the data on one line of the form:

POLine.java

package article2;

public class POLine {
  private String partNumber;
  private String quantity;
  private String price;
  private double total;

  public String getPartNumber() {
    return partNumber;
  }

  public void setPartNumber(String partNumber) {
    this.partNumber = partNumber;
  }

  public String getQuantity() {
    return quantity;
  }

  public void setQuantity(String quantity) {
    this.quantity = quantity;
  }

  public String getPrice() {
    return price;
  }

  public void setPrice(String price) {
    this.price = price;
  }

  public double getTotal() {
    return total;
  }

  public void setTotal(double total) {
    this.total = total;
  }

}

With this in place, we change our struts-config.xml so that the form uses an array of these beans, rather than explicit values for each field.

struts-config.xml version 2

<struts-config>
 <form-beans>
   <form-bean name="purchaseOrderBeanForm" type="org.apache.struts.action.DynaActionForm">
     <form-property name="lines" type="article2.POLine[]" size="4" />
   </form-bean>
  </form-beans>
 <action-mappings>
  <action path="/generateBeanPOForm" forward="/purchaseOrderBean.jsp"
          name="purchaseOrderBeanForm" scope="request"/>
  <action path="/generateBeanPO" type="article2.GenerateBeanPO"
          name="purchaseOrderBeanForm" scope="request" input="/purchaseOrderBean.jsp">
      <forward name="success" path="/displayPurchaseOrderBean.jsp" redirect="false"/>
  </action>
 </action-mappings>
 <message-resources parameter="ApplicationResources"/>
</struts-config>

A few notes are required here in explanation. The size parameter is late addition to Struts 1.1, it works like this.  If the type parameter of a form-property is an array, and the size parameter is present, Struts will construct an array of that size, and use the zero-argument instatiator of the class to populate the array.  In this case, it will create a 4-element array of type POLine and use the POLine() instantiator to create values for each slot.  If you don't specify a size parameter, no array will be created (useful for dynamically generated forms, which will be discussed later in this article.)

In order to have the array available for the form, we need to precreate it before the JSP page is displayed.  To do this, we create an action called /geneateBeanPOForm which doesn't actually call an Action class.  Instead, it instantiates the form in request scope and then immediately forwards to purchaseOrderBean.jsp.

purchaseOrderBean.jsp

<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib prefix="fmt" uri="/WEB-INF/fmt.tld" %>
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<head>
<title>Example of a Bean Based Purchase Order</title>
</head>
<h1>Example of a Bean Based Purchase Order</h1>
<html:form action="/generateBeanPO">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>
<c:forEach var="lines" items="${purchaseOrderBeanForm.map.lines}" >
  <TR><TD><html:text indexed="true" name="lines" property="partNumber"/></TD>
  <TD><html:text indexed="true" name="lines" property="quantity"/></TD>
  <TD><html:text indexed="true" name="lines" property="price"/></TD></TR>
</c:forEach>
</table>
<html:submit/>
</html:form>

Notice how much cleaner this code is.  We use the JSTL forEach iterator to get a handle on each element of the POLine array, and bind it to the variable lines.  Then we can use the indexed parameter of the html:text tag to indicate that each field is actually being indexed by the surrounding forEach loop.  A quirk of the html:text tag is that the name parameter (and by extension, the variable you use in the var field of the forEach) must match the name of the form-property.

When this is executed, the resulting HTML looks like:

<head>
<title>Example of a Bean BasedPurchase Order</title>
</head>
<h1>Example of a Bean Bease Purchase Order</h1>
<form name="purchaseOrderBeanForm" method="post"  action="/jdj/generateBeanPO.do">
<TABLE WIDTH="80%" BORDER="1">
<TR><TH align="left">Part Number</TH>
<TH align="left">Quantity</TH>
<TH align="left">Price</TH></TR>

<TR><TD><input type="text" name="lines[0].partNumber" value=""></TD>
<TD><input type="text" name="lines[0].quantity" value=""></TD>
<TD><input type="text" name="lines[0].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[1].partNumber" value=""></TD>
<TD><input type="text" name="lines[1].quantity" value=""></TD>
<TD><input type="text" name="lines[1].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[2].partNumber" value=""></TD>
<TD><input type="text" name="lines[2].quantity" value=""></TD>
<TD><input type="text" name="lines[2].price" value=""></TD></TR>

<TR><TD><input type="text" name="lines[3].partNumber" value=""></TD>
<TD><input type="text" name="lines[3].quantity" value=""></TD>
<TD><input type="text" name="lines[3].price" value=""></TD></TR>

</table>
<input type="submit" value="Submit">
</form>

Notice that nowhere in the JSP do we specify the number of lines, the forEach loop figures it out automatically.  This means that if you want to add more lines, you just change the size parameter, and it all updates automatically.

Finishing things out, here's the Action to process the form submission:

GenerateBeanPO.java

package article2;

import org.apache.struts.action.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;

import article2.Utils;

public class GenerateBeanPO extends Action {
   public ActionForward execute(ActionMapping mapping, ActionForm form,
                                HttpServletRequest request, HttpServletResponse response)
     throws ServletException, IOException {

     DynaActionForm poForm = (DynaActionForm) form;
     POLine[] lines = (POLine[]) poForm.get("lines");
     for (int i = 0; i < lines.length; i++) {
       if (!Utils.nullOrBlank(lines[i].getPartNumber())) {
           try {
              double total = Integer.parseInt(lines[i].getQuantity()) *
              Double.parseDouble(lines[i].getPrice());
              lines[i].setTotal(total);
           } catch (NumberFormatException ex) {
           }
        }
      }

     return mapping.findForward("success");
     }
}

Once again, the code is totally general, there's no need to specify how many lines to iterate over.

You can even dynamically specify the size of the form (and prepopulate it) at run time.  I've recently used this technique with an insurance application where the user has to choose various parameters such as copays and deductables for a claim modeling system.  by using an Action instead of a forward to precreate the form-property value, and leaving the size parameter out.  The actual can then create an array of the appropriate size and prepopulate values.  The only trick is that the form must be placed in session scope, because it needs to be available and of the right size to accept the submitted values.

In the next edition of Succeeding with Struts, we'll tie this all together by looking at how to use the Validator Framework to handle both simple and bean-based validation, and take a look at the new validWhen validation which has just been added to Struts.

About the author

James Turner is the Director of Software Development for Benefit Systems, Inc. He is also a contributor to the Apache Struts project. He has two books out on web-facing Java technologies, MySQL and JSP Web Applications, and Struts Kick Start. His third book, Java Server Faces Kick Start, will be published by Sams in the 4th quarter of 2003.




Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel