Home
Download
Manual
Nested Beans
Mailing Lists
FAQ

Nested Beans with FormDef

Note: The code samples shown in this guide uses syntax for FormDef 0.6 for Struts 1.2.4 or later.

Using FormDef to declare a form bean whose fields map directly to a business object is straightforward.

Given the following employee object (formatted for brevity):

package my.package;

public class Employee {
    private int number;
    private String name;
    private String address;
    private double salary;

    public int getNumber() { return number; }
    public void setNumber(int number) { this.number = number; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public String getAddress() { return address; }
    public void setAddress(String address) { this.address = address; }
    
    public double getSalary() { return salary; }
    public void setSalary(double salary) { this.salary = salary; }
}

and the following configuration:
    <form name="employeeForm"
        beanType="my.package.Employee">
        
FormDef will declare a form bean equivalent to the following:
    <form-bean name="employeeForm" type="org.apache.struts.action.DynaActionForm">
        <form-property name="number" type="java.lang.String"/>
        <form-property name="name" type="java.lang.String"/>
        <form-property name="address" type="java.lang.String"/>
        <form-property name="salary" type="java.lang.String"/>
    </form-bean>
    
    
All form fields default to the java.lang.String type. The provided getFormValues() and setFormValues() will use the included converters to format and parse data between the form bean and the business object.

In certain cases, it becomes necessary to have a form represent a business object that is composed of other business objects. FormDef's default behavior of using Strings for all fields will apply to these as well. To change the type used for a form field, it should be specified in the field's configuration when the form is declared.

Let's say that the address property of the Employee business object uses the following Address class:

package my.package;

public class Address {    
    private String street;
    private String city;
    private String state;
    private int zip;

    public String getStreet() { return street; }
    public void setStreet(String street) { this.street = street; }
    
    public String getCity() { return city; }    
    public void setCity(String city) { this.city = city; }
    
    public String getState() { return state; }
    public void setState(String state) { this.state = state; }
    
    public int getZip() { return zip; }
    public void setZip(int zip) { this.zip = zip; }
}

Our field in Employee has the usual declarations:
public class Employee {
    ....
    
    private Address address;

    public Address getAddress() { return address; }
    public void setAddress(Address address) { this.address = address; }
    
}

The configuration of the employee form bean can be modified to work with the nested object. To do this, we declare a form called addressForm to hold the address fields. On the employeeForm, we will specify that the address property is associated with a FormDef form by specifying a formName attribute.

Our FormDef configuration will change to:

    <!-- here's our configuration for the Address bean -->
    <form name="addressForm"
        beanType="my.package.Address"/>

    <form name="employeeForm"
        beanType="my.package.Employee">

        <!-- specify that our address field should use addressForm -->
        <field property="address" formName="addressForm"/>

    </form>
    
With addressForm declared, and employeeForm told about it, FormDef will now use that information to handle the nested form.

It is important to note that when using this technique, the form bean should be prepopulated and placed in session scope (the default setting in Struts). This allows Struts to reuse the form definition that was initialized before the form was created, with the employee field containing the correct dyna properties.

There's no change in the calls to prepopulate the form and to retrieve its values.

To prepopulate the form:

    // pass the data from the employee bean to the form
    ActionForm employeeForm = 
            FormUtils.setFormValues("employeeForm", employee,
                    this, mapping, request);

    // make the form available to the JSP
    request.getSession().setAttribute("employeeForm", employeeForm);
    
If we want an empty form, we can use the createActionForm() method in FormUtils:
    // create our empty form bean
    ActionForm employeeForm = FormUtils.getInstance()
            .createActionForm("employeeForm", this, mapping, request);

    // pass our form to the JSP
    request.getSession().setAttribute("employeeForm", employeeForm);

To retrieve values from the form:

    // get the employee values from the submitted form 
    Employee employee = (Employee) 
            FormUtils.getFormValues(form, this, mapping, request);
            
In our JSP, the field names reflect the nesting of the forms:
    <html:form action="/saveEmployee">
    <html:hidden property="number"/>
    <TABLE>
    <TR><TD> Employee Number </TD>
        <TD> <bean:write name="employeeForm" property="number"/> </TD>
        </TR>
    <TR><TD> Name </TD>
        <TD> <html:text property="name"/> </TD>
        </TR>
    <TR><TD> Street Address </TD>
        <TD> <html:text property="address.street"/> </TD>
        </TR>
    <TR><TD> City </TD>
        <TD> <html:text property="address.city"/> </TD>
        </TR>
    <TR><TD> State </TD>
        <TD> <html:text property="address.state"/> </TD>
        </TR>
    <TR><TD> Zip Code </TD>
        <TD> <html:text property="address.zip"/> </TD>
        </TR>
    <TR><TD> Salary </TD>
        <TD> <html:text property="salary"/> </TD>
        </TR>
    </TABLE>
    </html:form>
    
    
To validate the form, we use the Validator plug-in with the following configuration:
    <form name="employeeForm">
        <field property="name" depends="required">
            <arg0 key="employeeform.name.displayname"/>
        </field>
        <field property="address.street" depends="required">
            <arg0 key="employeeform.address.street.displayname"/>
        </field>
        <field property="address.city" depends="required">
            <arg0 key="employeeform.address.city.displayname"/>
        </field>
        <field property="address.state" depends="required">
            <arg0 key="employeeform.address.state.displayname"/>
        </field>
        <field property="address.zip" depends="required,integer">
            <arg0 key="employeeform.address.zip.displayname"/>
        </field>
        <field property="salary" depends="required,mask">
            <arg0 key="employeeform.salary.displayname"/>
            <var>
                <var-name>mask</var-name>
                <var-value>^\$?\d+(,\d{3})*(\.\d{2})?$</var-value>
            </var>
        </field>
    </form>