FormDef Manual


Note: This manual was generated from a development build of FormDef 1.0-dev-3.
The downloads for this version are:
formdef-1.0-dev-3.zip - binary distribution
formdef-1.0-dev-3-src.zip - source distribution
formdef-1.0-dev-3-lib.zip - library distribution

Installation


Declaring Forms


Customizing form fields


Converters


Nested Beans


FormUtils


Combining FormDef and Validator configuration



  Installation

  Download FormDef plug-in

    FormDef is available at here. You can choose to download the binary or source distribution.


  Accessing the CVS Source

    The FormDef source is hosted at the java.net project site. To checkout the source, you can use the following command:
    cvs -d :pserver:guest@cvs.formdef.dev.java.net:/cvs checkout -P formdef
            


  Copy formdef.jar to your library directory

    Copy formdef.jar to your WEB-INF/lib directory, or whereever you keep the struts jars. Make sure that formdef.jar is available in the classpath of the web application that will use it.


  Create a <plug-in> entry for FormDef

    You need to add a <plug-in> entry in your struts config file just as you would for Tiles and Validator. As with Tiles and Validator, FormDef requires an XML file for configuration.

To specify an XML that uses the standalone XML format, use the pathnames plug-in property:
    <plug-in className="formdef.plugin.FormDefPlugIn">
        <set-property property="pathnames"
                value="/WEB-INF/form-defs.xml"/>
    </plug-in>
            

Note: FormDef supports a standalone XML format and a combination XML format which you can use to combine form definitions and form validations. The combination XML format is useful if you use the Validator plugin and would like to keep the definition and validation information in one place. For more information, see Combining FormDef and Validator configuration.



  Declaring Forms

  Declaring a form bean

    To declare a form bean using FormDef, simple add a <form> entry in the configuration file, provide the name for your form bean, and the name of the class from which the form bean will be patterned after.:
    <form-definition>
        <formset>
            <form name="myForm"
                beanType="com.my.dto.MyBean"/>
        </formset>
    </form-definition>
            
When the plugin gets executed, it will create a DynaActionForm containing String fields for each property of MyBean. The JavaBeans specification is used to identify the fields that MyBean has. For fields that do not follow the JavaBeans specs, see Specifying field accessor methods.


  Selecting the ActionForm type

    FormDef defines DynaActionForm form beans by default. This default can be overridden by form or by module. To change the form type for a single form, provide a formType attribute in the form declaration.
    <form-definition>
        <formset>
            <form name="myForm"
                beanType="com.my.dto.MyBean"
                formType="org.apache.struts.validator.DynaValidatorForm"/>
        </formset>
    </form-definition>
            
To change the default type used for all form beans, add a formdef-config element:
    <form-definition>
        <formdef-config
            formType="org.apache.struts.validator.DynaValidatorForm"/>

        <formset>
        ...
        </formset>
    <form-definition>
            
The global declaration changes the default type. Individual form beans can still specify a different formType as needed.


  Specifying a factory for your business object

    When you use FormUtils.getFormValues() to convert the form bean to a business object, the default behavior is to use the business object's no-arg constructor. This behavior can be changed by specifying a class that will act as a factory for your business object:
    <form name="personForm" beanType="com.my.dto.Person">
        <factory type="com.my.dto.PersonFactory"
            method="getPerson"
            nameParam="true"/>
        ...
    <form/>
            
For the above example, when FormUtils needs an instance of personForm, it will call the getPerson method of the PersonFactory class and pass it the name of form bean as the parameter. Note that PersonFactory doesn't need to implement any interface and the actual method can be given any name.

The factory method may or may not accept parameters, and in the case where it does, it has to be a single String identifying the name of the form bean to be instantiated. The return value must be an instance of the business object for that form bean.



  Customizing form fields

  Specifying field accessor methods

    Though FormDef creates form bean declarations using the JavaBean conventions for identifying fields, it can be configured to use methods that do not follow the getXXX and setXXX conventions:
    <form name="sampleForm"
        beanType="com.my.dto.SampleBean">
        <field property="specialDate"
            getter="getLastSpecialDate"
            setter="addSpecialDate"/>
    </form>                
            
In the above example, a specialDate field is added to the form bean declaration. During formatting and parsing operations, the getLastSpecialDate method is called to retrieve the current specialDate, and addSpecialDate is called to pass the value from the form bean to the business object.


  Declaring form fields that are not in the business object

    Fields can be added to the form bean declaration even when there are no equivalents for that field in the business object. These can be used when the form works with controls that are not needed by the business tier.
    <form name="sampleForm"
        beanType="com.my.dto.SampleBean">
        <field property="selectedButton"/>
    </form>                
            
The above example will add a selectedButton field in the sampleForm form bean, along with the fields from SampleBean.


  Field formatting and parsing

    FormDef defines a Converter interface for formatting and parsing data between business objects and form beans. Default converter implementations are provided for the primitive, String, and Date types. More information about converters are provided here.


  Specifying the type of individual fields

    When FormDef configures a business object's form bean equivalent, all fields are given a type of java.util.String by default. This can be overridden by providing a type attribute to a field.
    <form name="memberForm"
        beanType="com.my.dto.Member">
        <field property="inactive"
            type="java.lang.Boolean"/>
    </form>                
            


  Excluding bean fields from the generated form

    FormDef will create a field on the form for each property on the bean. To exclude select fields, specify the field in an <excludes> element.
    <form name="memberForm"
        beanType="com.my.dto.Member">
        <excludes>createDate, updateDate</excludes>
    </form>                
            
In the example above, the createDate and updateDate fields of Member will not have corresponding fields on memberForm. Fields listed in an <excludes> element will not be included on the form even when there is a <field> element configured for it.

Optionally, a field can be configured for exclusion using the exclude attribute of <field> element.
    <form name="memberForm"
        beanType="com.my.dto.Member">
        <field property="createDate" exclude="true"/>
        <field property="updateDate" exclude="true"/>
    </form>                
            



  Converters

  Specifying conversion formats

    The default converters that are provided use DecimalFormat and SimpleDateFormat for formatting and parsing numeric and date fields. Therefore, to format these types of data, you can use format strings for DecimalFormat and SimpleDateFormat:
    <field property="doubleField">
        <converter param="###,###,##0.000"/>
    </field>
    <field property="dateField">
        <converter param="yyyy-MM-dd"/>
    </field>
            
Numeric data can be handled without specifying a format String. Date fields, on the other hand, require format Strings and one should always be provided when a form contains any type of Date object. Global converters can be used to specify field formats that can be used for all forms.


  Localized formats

    Conversion formats can be placed in the application's message resource files instead of being specified directly in the form-defs.xml file. Instead of specifying the param attribute, a key attribute can be given instead. Message format strings can also be placed in a separate resource bundle. These formats can be specified by using the bundle attribute.
    <field property="doubleField">
        <converter key="currencyFormat"/>
    </field>
    <field property="dateField">
        <converter key="dateFormat"
            bundle="bundle2"/>
    </field>
            
In the example above, the currencyFormat key is used to find the format to be applied for the doubleField property of the form. For the dateField property, the dateFormat key is used on the the bundle2 resource bundle.

Specifying conversion formats this way allows an application supporting multiple locales to adjust the data format used for each locale.


  Using global converters

    An alternative to specifying conversion formats per field is to declare global converters. Global converters are useful when more than one field uses the same format or the same converter, or multiple fields need to have the same format even when the format changes.
There are three types of global converters that can be declared: property type converters, property name converters, and named converters.
Each global converter declaration can specify either a conversion format to apply, a converter class to use, or both.


  Property type global converters

    Property type global converters are applied to all fields of a certain type. An example of this would be declaring the Date converter that's declared to handle Date fields.
    <form-definition>
        <global-converters>
            <global-converter for="property-type"
                target="java.util.Date"
                param="MM/dd/yyyy"/>
        </global-converters>
    </form-definition>
            
With the above declaration, FormDef would use the "MM/dd/yyyy" string to format and parse all Date fields.

Another use would be to declare a conversion class to use for a custom field type:
    <global-converter for="property-type"
        target="formdef.plugin.example.employee.business.EmploymentCode"
        type="formdef.plugin.example.employee.web.EmploymentCodeConverter"/>
            
The above declaration would use the EmploymentCodeConverter class to convert all fields that have the EmploymentCode type.


  Property name global converters

    Property name global converters are applied to all fields with a certain name. This is useful when you have multiple forms which has a certain field that needs to be formatted a certain way:
    <global-converter for="converter-name"
        target="salary"
        param="###,###,##0.00"/>                
            
The above declaration would format all salary fields in all forms using the ###,###,##0.00 format string.


  Named global converters

    Named global converters can be used to associate a global converter with an identifier and share its configuration for multiple fields. This can be seen as an alternative to property name global converters when the fields that need to share configurations do not share the same name:
    <global-converter for="converter-name"
        target="salaryConverter"
        param="###,###,##0.00"/>
            
The above global-converter entry declares a converter configuration with the name "salaryConverter". This converter can be associated with fields in form declarations:
    <form name="sampleForm"
        beanType="com.my.dto.SampleBean">
        <field property="desiredSalary">
            <converter name="salaryConverter"/>
        </field>
    </form>
            



  Nested Beans

  Generating fields for nested beans

    A business bean may contain a nested bean. FormDef can configure a nested form for the nested bean by associating it with another FormDef-defined form defined for the bean being nested.

For example, we have an Employee bean with an address field. The address field uses a separate Address bean to hold individual street, city, state, and zip values:
    <!-- 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>
            
Internally, a special type of converter, FormConverter, is associated with the field, and uses the form name as conversion parameter to find the FormDef mapping.


  Generating fields for a collection of nested beans

    For a business bean that contains a collection of business beans, a form that contains a collection of nested forms may be needed. To configure FormDef for this:

  1. Configure a form bean counterpart for the nested bean.
  2. Configure the field that holds the collection of nested beans to the desired Collection type.
  3. Assign a FormCollectionConverter implementation as this field's converter.
  4. Specify the name of the form bean counterpart for the nested bean as a conversion parameter for FormCollectionConverter
To use our previous example, let's say that an employee is allowed several addresses.
    <!-- here's our configuration for the Address bean -->
    <form name="addressForm"
        beanType="my.package.Address"/>  <-- (1) -->

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

        <!-- specify that our address field should use ArrayList -->
        <field property="address" type="java.util.ArrayList">
                                        <-- (2) -->
            <converter
                type="formdef.plugin.conversion.FormCollectionConverter"
                      <-- (3) -->
                param="addressForm"/>
                      <-- (4) -->
        </field>

    </form>
            

The AddressConverter is an implementation of the FormDef-provided FormCollectionConverter which will loop through the collections of the bean and create their counterpart forms, and vice versa, during conversion.

When creating the collection for either the bean or the form, FormCollectionConverter will use the declared type for the field to instantiate a Collection. In the example above, the "address" Collection will be set to an instance of "java.util.ArrayList". Similarly, on the my.package.Employee bean, its "address" Collection will be set to an instance of its declared type.

If the declared types on either the form or the bean are abstract classes, such as java.util.List or java.util.Set, additional properties can be configured to specify which implementation class will be used for the field.
    <form name="employeeForm"
        beanType="my.package.Employee">

        <field property="address" type="java.util.List">
            <converter
                type="formdef.plugin.conversion.FormCollectionConverter"
                param="addressForm">
                <set-property key="formType" value="java.util.ArrayList/>
                <set-property key="beanType" value="java.util.ArrayList/>
            </converter>
        </field>

    </form>
            

In the example above, "address" is declared as the abstract class "java.util.List", with the "formType" converter property is used to tell FormCollectionConverter which List implementation to instantiate.

Likewise, the my.package.Employee object may itself declare its "address" field to be an abstract class such as "java.util.List" or even "java.util.Collection". The "beanType" converter property is then used to indicate which concrete class to use when populating this field.


  A note about using nested forms

    It is important to note that when using the strategies described above, 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 nested form field containing the correct dyna properties.



  FormUtils

  FormUtils.setFormValues()

    FormUtils.setFormValues() can be used to initialize a form bean with values from a business object:
    DynaActionForm dynaForm = (DynaActionForm) 
            FormDefUtil.setFormValues("employeeForm", employee,
                    this, mapping, request);
            
The first parameter identifies the form definition that will be used.
The second parameter is the business object which contains the values that will be used to populate the form bean.
The last three parameters are the current Action object, the current action mapping, and the request being processed.

FormUtils.setFormValues() returns the populated form bean. This can then be placed by the caller in the proper scope with the proper name so that Struts can find it when rendering an HTML form.


  FormUtils.getFormValues()

    FormUtils.getFormValues() can be used to create a populated business object from a form bean:
    Employee employee = (Employee)
            FormDefUtil.getFormValues(form, this, mapping, request);
            
The first parameter is the form passed by Struts to the action object.
The next three parameters are the current Action object, the current action mapping, and the request being processed.

getFormValues() returns the business object populated with values from the form bean. If a factory is associated with the form definition, it is called to create the business object that will be populated. Otherwise, the business object's no-arg constructor will be used to create the object.


  Other FormUtils methods

    FormUtils provides instance methods which break down the operation of setFormUtils() and getFormUtils. These can be used for more control in the formatting and parsing of form data. Consult the FormUtils class for more details.


  Overriding FormUtils

    The static FormUtils methods setFormUtils() and getFormUtils are implemented by instance methods that can be overridden. To accomplish this, a FormUtils subclass can be provided with the overriding method implementations. A FormUtilsFactory implementation must be provided to return the FormUtils subclass that should be used.

The register the custom FormUtilsFactory implementation, the formUtilsFactory attribute of the formdef-config element is provided:
    <form-definition>
        <formdef-config
            formUtilsFactory ="com.my.FormUtilsFactory"/>
        ...
    <form-definition>
            



  Combining FormDef and Validator configuration

  Combining Form Definition and Form Validation configuration information

    FormDef provides a DTD that can be used to combine the form definitions and form validation rules in one XML file. The combined configuration DTD does not rename any element, making it very easy to change an existing standalone Validator config file or FormDef config file to use the combined format. Furthermore, existing Validator configuration files can be used with configuration files using the mixed format.

Note: A separate formdef-validator.jar provides this functionality.


  The combined <form> element

    The easiest way to show how the two configurations can be combined is to provide an example.

Here's an example form definition:
    <form-definition>
        <formdef-config
            formType="org.apache.struts.validator.DynaValidatorForm"/>
                
        <global-converters>
            <global-converter for="property-type"
                target="java.util.Date"
                param="MM/dd/yyyy"/>
        </global-converters>
                
        <formset>
            <form name="employeeForm"
                beanType="formdef.plugin.example.employee.business.Employee">
                <field property="salary">
                    <converter param="###,###,##0.000"/>
                </field>
            </form>
        </formset>
    </form-definition>
            
The above example declares that the DynaValidatorForm should be used as the default form type. It also declares a global format string for Date fields, and declares an employeeForm with a format string for the salary field.

Here's the validation config for the form:
    <form-validation>
        <form name="employeeForm">
            <field property="birth" depends="required,date">
                <arg0 key="employeeform.birth.displayname"/>
                <var>
                    <var-name>datePatternStrict</var-name>
                    <var-value>MM/dd/yyyy</var-value>
                </var>
            </field>
            <field property="salary" depends="required,mask">
                <arg0 key="employeeform.salary.displayname"/>
                <var>
                    <var-name>mask</var-name>
                    <var-value>^(?:\d+|\d{1,3}(?:,\d{3})*)(?:\.\d{1,2}){0,1}$</var-value>
                </var>
            </field>
        </form>
    </form-validation>
            
The above config validates that the birth and salary fields have the right format. Combining the two configurations, we have:
    <form-definition>
        <formdef-config
            formType="org.apache.struts.validator.DynaValidatorForm"/>

        <global-converters>
            <global-converter for="property-type"
                target="java.util.Date"
                param="MM/dd/yyyy"/>
        </global-converters>

        <formset>
            <form name="employeeForm"
                beanType="formdef.plugin.example.employee.business.Employee">
                <field property="birth" depends="required,date">
                    <arg0 key="employeeform.birth.displayname"/>
                    <var>
                        <var-name>datePatternStrict</var-name>
                        <var-value>MM/dd/yyyy</var-value>
                    </var>
                </field>
                <field property="salary" depends="required,mask">
                    <converter param="###,###,##0.000"/>
                    <arg0 key="employeeform.salary.displayname"/>
                    <var>
                        <var-name>mask</var-name>
                        <var-value>^(?:\d+|\d{1,3}(?:,\d{3})*)(?:\.\d{1,2}){0,1}$</var-value>
                    </var>
                </field>
            </form>
        </formset>
    </form-definition>
            
The <global-converter> remains as is, and that's because there's no equivalent in Validator. The form declaration now combines the definition and validation information. The <form> has the bean-name attribute for FormDef. The <field>s have the depends attribute for Validator, the <converter> element for FormDef, and the <arg0>, and <var> elements for Validator.

Be aware that properties of the Employee class that do not have validation rules will not be validated but are still declared in the resulting form bean.

This example is based on the Employees example application.


  The plug-in declarations for the combined configuration files

    To use the combined configuration files, minor changes in the struts-config.xml file are needed.

First, for FormDef, you need to use the new CombinedFormDefPlugIn.

Second, for Validator, the FormDef-supplied CombinedValidatorPlugIn extension should be used instead of the default ValidatorPlugIn that comes with Struts.

In both cases, the "Combined-" version of the plugins do not provide any change in functionality except for providing a way to pass the data in the combined files to their respective engines.
    <plug-in className="formdef.plugin.validator.CombinedFormDefPlugIn">
        <set-property property="pathnames"
            value="/WEB-INF/form-defs-validator-employees.xml"/>
    </plug-in>
    <plug-in className="formdef.plugin.validator.CombinedValidatorPlugIn">
        <set-property property="pathnames"
            value="/WEB-INF/validator-rules.xml,/WEB-INF/form-defs-validator-employees.xml"/>
    </plug-in>
            
Notice that the CombinedValidatorPlugIn is also passed the validator-rules.xml which contains the standard declarations that come with the Validator plug-in. It can accept existing validator.xml files that are using the default Validator DTD as well as those containing the combined format.


  Hiding forms and fields from Validator

    With the combined definition and validation file, all forms that are defined are also passed to the Validator by default. Also, all fields declared are passed to the Validator as well. However, some forms being defined may not need validation, and some fields may not need validation but have to be specified to include definition parameters.

You can specify forms or fields that should not be passed to the Validator by adding validate=false in a form or field's declaration.
    <form name="mySimpleForm"
            beanType="com.my.dto.MySimpleBean"
            validate="false">
        <field name="someField"/>
    </form>
    <form name="myOtherForm"
        beanType="com.my.dto.MyOtherBean>
        <field name="someField" depends="required"/>
        <field name="selectedButton"
            validate="false"/>
    </form>
            
In the example given above, mySimpleForm configures a form bean definition but specifies that it should not be passed to the Validator engine.

A second form, myOtherForm, has one field that requires validation, someField. Its associated business object may contain several other fields, and FormDef will create a form bean which contains all those fields. Because those fields aren't declared here, Validator will not "see" those other fields. However, an additional field, selectedButton, is being added to the form bean. To prevent Validator from "seeing" this field, validate=false is added to its declaration.


  Hiding forms from FormDef

    With the combined definition and validation file, just as with Validator, all forms that are defined are also passed to FormDef by default. This behavior can be overridden on a per-form basis by adding generate=false in a form's declaration.
    <form name="myExistingForm"
            generate="false">
        ...
    </form>
            
In the example given above, myExistingForm defines validation rules for a similarly named form. The generate=false attribute will tell the CombinedFormDefPlugIn not to generate a form bean for this form, which could be defined in struts-config.xml or is a ValidatorActionForm subclass.