Previous Section  < Day Day Up >  Next Section

7.2 Using the Standard Validators

The expense report entry form part of the sample application contains a number of fields that require validation: all fields must have a value, a syntactically valid date must be entered in the Date field, and the Amount field must contain an amount larger than one. Figure 7-1 shows all these fields, with a couple of error messages, generated by the initial implementation of this form.

Figure 7-1. The first version of the report entry form area JSP page
figs/Jsf_0701.gif

The JSP file for this version of the form is shown in Example 7-2.

Example 7-2. Initial report entry form area JSP page (expense/stage1/entryFormArea.jsp)
<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>



<f:view>

  <h:form>

    Title: 

    <h:inputText id="title" size="30" required="true"

      value="#{reportHandler.currentReport.title}" />

    <h:message for="title" />

    <br>

    Entry:

    <br>

    Date: 

    <h:inputText id="date" size="8" required="true"

      value="#{entryHandler.currentEntry.date}">

      <f:convertDateTime dateStyle="short" />

    </h:input_text>

    <h:message for="date" />

    <br>

    Type:

    <h:selectOneMenu id="type" required="true"

      value="#{entryHandler.currentEntry.type}">

      <f:selectItems value="#{entryHandler.expenseTypeChoices}"/>

    </h:selectOneMenu>

    <h:message for="type" />

    <br>

    Amount:

    <h:inputText id="amount" size="8" required="true"

      value="#{entryHandler.currentEntry.amount}">

      <f:convertNumber pattern="#,##0.00" />

      <f:validateDoubleRange minimum="1"/>

    </h:input_text>

    <h:message for="amount" />

    <br>

    <h:commandButton value="Add" 

      disabled="#{reportHandler.addEntryDisabled}"/>

  </h:form>

</f:view>

The Title field input component is represented by an <h:inputText> action in Example 7-2. The only validation requirement for this component is that a value must be entered; the format of the value doesn't matter. This requirement is declared by setting the required attribute to true, causing the action's tag handler to call setRequired(true) on the corresponding UIComponent when the view is created.

When the user submits the form, JSF processes the request as described in the previous section. If the validate( ) method can successfully convert the submitted value to the native data type, it moves on to semantic validation. If the required property is set to true, the validate() method verifies that there's a submitted value. If there isn't, the method marks the component value as invalid and adds an error message to the FacesContext message list, just as when the value can't be converted.

This leads us to the next piece of the puzzle. Note that I assign an ID to the input component with the id attribute, and that this ID is also used as the value of the for attribute in the <h:message> action element that follows the Title <h:inputText> action. In Chapter 6, we used the <h:messages> (plural) action to display error messages for all components in one place. The <h:message> (singular) action displays messages for the single component specified by the for attribute, so a potential error message can be displayed next to the corresponding user interface widget.

The input component for the Date field is also declared as required and it is linked to an <h:message> action through a unique component ID. Additionally, a nested <f:convertDateTime> action configures the component with a DateTime converter. As described in the previous section, the converter ensures that the value the user enters is a syntactically correct date string. Otherwise, it adds an error message to the list, which the <h:message> action displays.

The Amount field input component is more interesting. Besides the required attribute and a number converter, a nested <f:validateDoubleRange> action configures this component with a javax.faces.validator.DoubleRangeValidator, a validator for testing if the value is a decimal number above or below certain values. In Example 7-2, only a minimum value is specified, but you can also specify a maximum value. Table 7-3 lists all standard validators. Appendix A describes the validator JSP actions in detail.

Table 7-3. Standard validators

JSP action

Default Class

Description

<f:validateDoubleRange>

javax.faces.validator.DoubleRangeValidator

Checks that a component value is a decimal above a minimum or a maximum

<f:validateLength>

javax.faces.validator.LengthValidator

Checks that the number of characters in the value is above a minimum or below a maximum.

<f:validateLongRange>

javax.faces.validator.LongRangeValidator

Checks that a component value is a nondecimal above a minimum or a maximum

When a component is configured to use a validator, the validate() method first checks if a value is submitted at all (if the required property is also set to true) and then asks the validator to validate the value. If the validator doesn't accept the value, the component value is marked as invalid and an error message is added to the FacesContext message list, just as for all other input validation errors.

You can configure a component with more than one validator. If there's more than one validator, they are called in the order they are added to the component.

7.2.1 Value Bindings for the Report Entry Fields

Besides the elements and attributes for conversion and validation, all JSF component action elements in Example 7-2 have value attributes that bind them to bean properties, in the same way as in the examples in Chapter 6.

The value binding expression for the Title is reportHandler.currentReport.title. The first part, reportHandler, is the variable for the ReportHandler we looked at in Chapter 6. This class has a property named currentReport that holds a reference to an instance of the Report class described in Chapter 5, which in turn has a property named title:

package com.mycompany.expense;

...

public class ReportHandler {

    private Report currentReport;

    ...

    public Report getCurrentReport( ) {

        if (currentReport == null) {

            currentReport = createNewReport( );

        }

        return currentReport; 

    }

    ...

    private Report createNewReport( ) {

        Report report = new Report( );

        report.setOwner(getCurrentUser( ));

        ...

        return report;

    }

    ...

}

package com.mycompany.expense;

...

public class Report implements Serializable {

    private String title;

    ...

    public synchronized String getTitle( ) {

        return title;

    }



    public synchronized void setTitle(String title) {

        this.title = title;

    }

    ...

}

The value binding is used as an lvalue when the submitted form is processed, setting the title property of the current report to the value of the UIInput component. When the response is rendered, it's used as an rvalue, so the UIInput component reads its value from the title property.

The components for the Date, Type, and Amount fields all have value bindings that bind them to properties of an instance of the ReportEntry class (presented in Chapter 5), available through another glue class called EntryHandler:

package com.mycompany.expense;

...

public class EntryHandler {

    private ReportEntry currentEntry;

    ...

    public ReportEntry getCurrentEntry( ) {

        if (currentEntry == null) {

            currentEntry = new ReportEntry( );

        }

        return currentEntry;

    }

    ...

}

package com.mycompany.expense;



import java.io.Serializable;

import java.util.Date;



public class ReportEntry implements Serializable {

    private Date date;

    private int type;

    private double amount;

    ...

    public Date getDate( ) {

        if (date == null) {

            date = new Date( );

        }

        return date;

    }



    public void setDate(Date date) {

        this.date = date;

    }



    public int getType( ) {

        return type;

    }



    public void setType(int type) {

        this.type = type;

    }



    public double getAmount( ) {

        return amount;

    }



    public void setAmount(double amount) {

        this.amount = amount;

    }

    ...

}

The Type field is represented by a UISelectOne component, with a renderer that renders it as a selection list. The value binding expression binds it to the type property of the current ReportEntry instance, exactly the same as for all the other components, but how it gets its list of choices is new:

    Type:

    <h:selectOneMenu id="type" required="true"

      value="#{entryHandler.currentEntry.type}">

      <f:selectItems value="#{entryHandler.expenseTypeChoices}"/>

    </h:selectOneMenu>

In Chapter 6, the choices for the Status checkboxes are listed right in the page, but here I use a <f:selectItems> action bound to an EntryHandler property named expenseTypeChoices instead. Using a value binding to pull in the choices makes it easy to customize the application. The expenseTypeChoices property has a java.util.List with javax.faces.model.SelectItem instances as its value:

package com.mycompany.expense;



import java.util.ArrayList;

import java.util.Iterator;

import java.util.List;

import java.util.Map;

import java.util.Map.Entry;

import javax.faces.model.SelectItem;

...

public class EntryHandler {

    private Map expenseTypes;

    private List expenseTypeChoices;

    ...

    public void setExpenseTypes(Map expenseTypes) {

        this.expenseTypes = expenseTypes;

    }



    public List getExpenseTypeChoices( ) {

        if (expenseTypeChoices == null) {

            expenseTypeChoices = new ArrayList( );

            Iterator i = expenseTypes.entrySet( ).iterator( );

            while (i.hasNext( )) {

                Map.Entry me = (Map.Entry) i.next( );

                expenseTypeChoices.add(new SelectItem(me.getValue( ), 

                        (String) me.getKey( )));

            }

        }

        return expenseTypeChoices;

    }

    ...

}

UISelectItem or UISelectItems child components represent the choices handled by UISelectOne and UISelectMany components, and the value of a UISelectItem or UISelectItems component is one or more SelectItem instances.

SelectItem is a bean with three properties: value, label, and description. The value property is the value that is included in the request when the choice is selected, and the label is the descriptive text displayed to the user. The description property isn't used by any of the standard renderers, but a custom renderer can use it, e.g., to display a more detailed description as a "tool tip." The SelectItem class provides constructors that let you set one or more of the property values. Here, I use a constructor that sets the value and label properties, and leave the description undefined.

The basic data type for the SelectItem value property must be the same as for the value of the selection component the SelectItem instance belongs to. For instance, the value property of a SelectItem representing a choice for a UISelectOne component with a value of type Integer or int must be of type Integer. If the types don't match, the selection component's value isn't rendered or updated correctly.


The getExpenseTypeChoices() method creates and returns a List of SelectItem instances with the value and label taken from a java.util.Map, set by the setExpenseTypes() method. This design makes it easy to customize the available choices. The Map is declared and initialized as a managed bean in the faces-config.xml file:

<faces-config>

  ...

  <managed-bean>

    <managed-bean-name>expenseTypes</managed-bean-name>

    <managed-bean-class>java.util.TreeMap</managed-bean-class>

    <managed-bean-scope>application</managed-bean-scope>

    <map-entries>

      <value-class>java.lang.Integer</value-class>

      <map-entry>

        <key>Breakfast</key>

        <value>1</value>

      </map-entry>

      <map-entry>

        <key>Lunch</key>

        <value>2</value>

      </map-entry>

      <map-entry>

        <key>Dinner</key>

        <value>3</value>

      </map-entry>

      ...

    </map-entries>

  </managed-bean>

  ...

</faces-config>

In addition to the bean variable name and the class name, I declare the bean's scope as application, because the expense types are the same for all users of the application. I use a java.util.TreeMap class to get it automatically sorted by the key values. The <map-entries> element contains a <value-class> element that says that the values should be created as Integer values, and then a <map-entry> element with nested <key> and <value> elements for each entry.

The entryHandler variable is also declared as a managed bean, with property declarations that tell JSF to initialize it with references to both the ReportHandler instance and the expense type Map instance:

  <managed-bean>

    <managed-bean-name>entryHandler</managed-bean-name>

    <managed-bean-class>

      com.mycompany.expense.EntryHandler

    </managed-bean-class>

    <managed-bean-scope>session</managed-bean-scope>

    <managed-property>

      <property-name>reportHandler</property-name>

      <value>#{reportHandler}</value>

    </managed-property>

    <managed-property>

      <property-name>expenseTypes</property-name>

      <value>#{expenseTypes}</value>

    </managed-property>

    ...

  </managed-bean>

Most of this should look familiar, because it's the same type of declaration that we looked at for the reportHandler in Chapter 6. The only difference is the <managed-property> element. This is how you tell JSF to initialize a bean's properties. Each element contains a <property-name> element that contains the property name and a <value> element that contains a JSF EL expression that evaluates to the value it should be set to. Instead of a JSF EL expression, you can use a static value string or a <map-entries> element to initialize a property of type Map, or <list-entries> to initialize a List or array property. I'll show you examples of some of these alternatives later. All options are described in Appendix E.

To tie it all together, when the #{entryHandler.expenseTypeChoices} expression is evaluated for the first time in a session, JSF looks up the entryHandler variable declaration in the faces-config.xml file and creates an instance of the EntryHandler class. It then tries to set all the declared properties and finds the reference to the expenseTypes variable. If this variable doesn't exist, it looks up its faces-config.xml declaration, creates an instance of the TreeMap, initializes it with all the declared entries, and saves a reference to the instance as an application scope variable. JSF then sets the EntryHandler instance's expenseType property to a reference to the TreeMap and saves the initialized EntryHandler instance as a session scoped variable named entryHandler. Now the evaluation moves on, resulting in a call to the getExpenseTypeChoices( ) method, which creates the List of SelectItem instances from the TreeMap entries and returns it. Voila! The UISelectOne now has access to its choices in the required format.

After all this, I must admit that the getExpenseTypeChoices( ) method could return the Map with the choices directly instead of converting it to a List with SelectItem instances. If you use a Map as the value for a UISelectItems component, it converts it to SelectItem instances with the keys as labels and the values as values internally. I'm doing the conversion explicitly in the EntryHandler class in order to show you what's really going on and also because it makes other examples in this book easier to implement. Unless you need to set the description properties for the SelectItem instances, or deal with internationalization (as shown in Chapter 11), using a Map directly may be the better alternative.

    Previous Section  < Day Day Up >  Next Section