Previous Section  < Day Day Up >  Next Section

6.2 Binding Components to Model Properties

In Example 6-1, the components save their current value and redisplay it when the page is reloaded. That's great, but in order for the backend code to get the values, it would need to locate the corresponding user interface components and ask them for their values. That would introduce an undesirable dependency between the user interface and the backend code.

To solve this problem, JSF lets you bind components directly to JavaBeans properties of any application class as well as to elements of an array or java.util.List, or java.util.Map[1] entry. When the component is rendered, it reads the property or element value and renders it; when a request is processed, it sets the property or element value to the current component value. This way, the backend code doesn't need to know anything about the user interface—it just works with the properties of the application model classes.

[1] If you come from a Struts background, note that you can use a Map similarly to how a DynaBean is used in Struts.

A JSF application typically uses two types of classes with JavaBeans properties: the real application beans representing application concepts and what I call "glue beans," which sit between the real application beans and the user interface. Others call the latter a "backing bean" or "code-behind file," but it all means the same thing.

The Report class introduced in Chapter 5 is an example of a real application bean. The ReportHandler class is an example of a "glue bean," partly shown in Example 6-2.

Example 6-2. Parts of the ReportHandler class
package com.mycompany.expense;



import java.util.Date;

...



public class ReportHandler {

    ...

    private Date from;

    private Date to;

    private int[] status;



    ...

    public Date getFrom( ) {

        if (from == null) {

            from = getPreviousMonth(new Date( ));

        }

        return from;

    }



    public void setFrom(Date from) {

        this.from = from;

    }



    public Date getTo( ) {

        if (to == null) {

            to = new Date( );

        }

        return to;

    }



    public void setTo(Date to) {

        this.to = to;

    }



    public String[] getStatus( ) {

        if (status == null) {

            if (isManager) {

                status = new int[1];

                status[0] = Report.STATUS_SUBMITTED;

            }

            else {

                status = new int[4];

                status[0] = Report.STATUS_OPEN;

                status[1] = Report.STATUS_SUBMITTED;

                status[2] = Report.STATUS_ACCEPTED;

                status[3] = Report.STATUS_REJECTED;

            }

        }



        // Convert the int[] to a String[] to match the SelectItem type

        String[] stringStatus = new String[status.length];

        for (int i = 0; i < status.length; i++) {

            stringStatus[i] = String.valueOf(status[i]);

        }

        return stringStatus;

    }



    public void setStatus(String[] stringStatus) {

        status = null;

        if (stringStatus != null) {

            status = new int[stringStatus.length];

            for (int i = 0; i < status.length; i++) {

                status[i] = Integer.valueOf(stringStatus[i]).intValue( );

            }

        }

    }

    ...

}

The ReportHandler class contains JavaBeans accessor methods for properties corresponding to the user interface input fields in the filtering criteria form: from, to, and status.[2] When the JSF components are bound to these properties, the properties automatically provide default values when the components are rendered the first time and are set to the values the user enters when the form is submitted. Backend logic can then access the values easily without knowing anything about the user interface components. As you will see later, this type of "glue bean" also often implements event handler methods that invoke the real application beans with values captured by the user interface.

[2] The status property is declared as a String[] in the public API, because it must match the data type used for the choice values created by the <f:selectItem> elements, but internally the status values are held by an int[] to match the data type used in the business logic classes. We'll return to these details in Chapter 7.

6.2.1 Using JSF EL Expressions

In Chapter 4, I introduced the JSP and the JSP 2.0 Expression Language (EL). JSP EL expressions are evaluated when the JSP page is processed—in other words, when the response is rendered.

JSF also supports an EL. Syntactically, the JSP 2.0 EL and the JSF EL are identical, but JSF may evaluate the EL expressions both when it processes input the user submitted (e.g., to update the application bean properties) and when it renders the response. This is the main difference between the JSF EL and the JSP EL.

To distinguish between a JSP EL expression and a JSF EL expression, you must use different delimiters for each type: a JSP EL expression is always written as ${expression}, while a JSF EL expression is written as #{expression}, i.e., with a #{ as the start delimiter instead of ${.None of the JSF custom action element attributes accept a JSP EL expression, only a JSF EL expression.


To bind a component's main value to a property of a bean or an element of another data structure, JSF uses a value binding expression. A value binding expression is the part of a JSF EL expression that evaluates to a bean property, a java.util.List or array element, or a java.util.Map entry. The entity a value binding points to can be both read and written. In programming lingo, this amounts to what's often called an rvalue (the right-hand side of an assignment expression) for read access and an lvalue (the left-hand side of an assignment expression) for write access.

Some components, such as an output component, represent a read-only value; there's no way a user can change the value. All components also have a number of properties in addition to their main value, for instance, the size of an input field and the number of rows and columns of a text area. You can use value binding expressions to set the value of a read-only component and component properties, but you can also use the complete JSF EL expression syntax, e.g., use a bean property value combined with an EL operator, like this: #{report.status == 'open'}. EL expressions mixed with text are also supported for this type of property:

<h:outputText value="#{report.startDate} to #{report.endDate}" />

As with the JSP EL, a JSF EL expression has access to a number of variables holding implicit objects. Compared to the variables exposed by the JSP EL, JSF removes pageScope and pageContext and adds two new variables for accessing JSF components present in a view and JSF context information: view and facesContext. All implicit object variables are listed in Table 6-2.

Table 6-2. Variables for implicit JSF EL objects

Variable name

Description

requestScope

A collection (a java.util.Map) of all request scope variables

sessionScope

A collection (a java.util.Map) of all session scope variables

applicationScope

A collection (a java.util.Map) of all application scope variables

param

A collection (a java.util.Map) of all request parameter values as a single String value per parameter

paramValues

A collection (a java.util.Map) of all request parameter values as a String array per parameter

header

A collection (a java.util.Map) of all request header values as a single String value per header

headerValues

A collection (a java.util.Map) of all request header values as a String array per header

cookie

A collection (a java.util.Map) of all request cookie values as a single javax.servlet.http.Cookie value per cookie

initParam

A collection (a java.util.Map) of all application initialization parameter values as a single String value per value

facesContext

An instance of the javax.faces.context.FacesContext class, providing access to various JSF context data

view

An instance of the javax.faces.component.UIViewRoot class, providing access to all components in the current view

The reason JSF excludes the pageScope and pageContext variables is that they exist only during processing of the JSP page, but JSF also needs to evaluate its EL expressions while processing user input, before any JSP page is processed. Another reason the variables are excluded is that JSF can be used with other presentation layer technologies besides JSP, in which there is no such thing as a page scope or a page context. The facesContext variable is basically a replacement for pageContext, providing access to the same information and more.

The implicit view variable holds a reference to the UIViewRoot component. Because all component classes follow the JavaBeans coding conventions, properties and children of a component can be accessed through this variable. For instance, a value binding expression for a property of the first child of the form in Example 6-1 looks like this:

#{view.children[0].children[0].valid}

Here, the first child (index 0) of the view is the form component, and its first child is the input component, which has a property named valid. JSF 1.0 doesn't provide any method that exposes the children by their ID, so you must access them by their numeric index.

You'll see plenty of examples of both complete JSF EL expression and value binding expressions later in this book so don't worry if it seems like magic at this point.

6.2.2 Using Value Binding Expressions

Let's improve the filtering criteria form by binding the fields to properties of the ReportHandler class. Example 6-3 shows the new filtering criteria page with the value binding expressions added.

Example 6-3. New filtering criteria form with value binding expressions added (expense/stage2/filterArea.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:messages layout="table" />

  <h:form>

    From: <h:inputText size="8" value="#{reportHandler.from}" />

    <br>

    To: <h:inputText size="8" value="#{reportHandler.to}" />

    <br>

    Status:

    <h:selectManyCheckbox value="#{reportHandler.status}">

      <f:selectItem itemValue="1" itemLabel="Open" />

      <f:selectItem itemValue="2" itemLabel="Submitted" />

      <f:selectItem itemValue="3" itemLabel="Accepted" />

      <f:selectItem itemValue="4" itemLabel="Rejected" />

    </h:selectManyCheckbox>

    <p>

    <h:commandButton value="Filter" />

  </h:form>

</f:view>

The value attributes for the input components contain value binding expressions that point to the corresponding properties in a ReportHandler instance available through a variable named reportHandler. If you run this page, you'll see that the default dates returned by the getFrom() and getTo( ) methods shown in Example 6-2 end up in the input fields, and the same goes for the default choices returned by the getStatus() method.

6.2.3 Converting Between Model and View Data Formats

Adding the value binding expression takes us one step closer to a real application, but we're not done yet. First, note that the date fields are too short to show the dates in the format in which they are presented. Also, if you try to submit the form, error messages regarding the dates are displayed at the top of the page. The error messages are displayed by the <h:messages> action I added at the top in Example 6-3, which I'll get back to in the next chapter.

The reason for the error messages is that the value is sent to the application as a string (an HTTP request parameter is always a string), but the data type for the from and to properties is java.util.Date. JSF takes care of conversion between strings and simple data types—numbers and Booleans—all by itself, but a date can be written in many different ways, so we need to give JSF a clue about how to interpret a string representing a date. Example 6-4 shows you how this is done.

Example 6-4. Filtering criteria form with date converters (expense/stage3/filterArea.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:messages layout="table"/>

  <h:form>

    From: 

    <h:inputText size="8" value="#{reportHandler.from}">

      <f:convertDateTime dateStyle="short" />

    </h:inputText>

    <br>

    To: 

    <h:inputText size="8" value="#{reportHandler.to}">

      <f:convertDateTime dateStyle="short" />

    </h:inputText>

    <br>

    Status:

    <h:selectManyCheckboxl value="#{reportHandler.status}">

      <f:selectItem itemValue="1" itemLabel="Open" />

      <f:selectItem itemValue="2" itemLabel="Submitted" />

      <f:selectItem itemValue="3" itemLabel="Accepted" />

      <f:selectItem itemValue="4" itemLabel="Rejected" />

    </h:selectManyCheckbox>

    <p>

    <h:commandButton value="Filter" />

  </h:form>

</f:view>

The nested <f:convertDateTime> actions added for both <h:inputText> actions in Example 6-4 create and configure converters for conversion between String and java.util.Date values and link them to the corresponding components.

The converters can be configured in many ways using optional attributes. Here, I use the dateStyle attribute. It accepts the values default, short, medium, long, and full, corresponding to the formatting styles with the same names supported by the java.text.DateFormat class. If you want to show the time, you can optionally specify a style for the time portion with the timeStyle attribute, using the same values as for the date portion.

The exact format for the date and time styles depends on the locale used for the page (we'll get back to locales in Chapter 11), but here are samples of what they look like with the English locale.

default
Sep 9, 2003 5:41:15 PM
short
9/9/03 5:41 PM
medium
Sep 9, 2003 5:41:15 PM
long
September 9, 2003 5:41:15 PM PST
full
Tuesday, September 9, 2003 5:41:15 PM PST

As an alternative to the style attributes, you can use the pattern attribute. It accepts a symbol pattern of the same type as the java.text.SimpleDateFormat class, e.g., yyyy-MM-dd for 2003-09-09.

JSF also provides an <f:convertNumber> action with similar attributes for precise interpretation and formatting of numbers (see Appendix A for details). In the rare event that you need to convert between strings and a type not supported out-of-the-box by JSF, you can create your own custom converter.

6.2.4 Creating Objects Automatically as They Are Needed

With the converters in place, Example 6-4 works great, but there's still a bit of magic left. You may be asking yourself how the reportHandler variable gets its value; it's actually done automatically by JSF when the variable is first needed. JSF creates and configures instances of classes defined in the JSF faces-config.xml file for variables that are referenced in a value expression. Let's take a closer look at this feature.

Example 6-5 shows the reportHandler variable declaration in the faces-config.xml file for the sample application, minus details that are not relevant for the examples in this chapter.

Example 6-5. ReportHandler declaration in faces-config.xml
<faces-config>

  ...

  <managed-bean>

    <managed-bean-name>reportHandler</managed-bean-name>

    <managed-bean-class>

      com.mycompany.expense.ReportHandler

    </managed-bean-class>

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

    ...

  </managed-bean>

  ...

</faces-config>

The <managed-bean> element declares a variable that the application uses. The variable is mapped to a class that complies with the JavaBeans specification rules on naming conventions for accessor methods and has a public no-arguments constructor—hence, the bean reference in the element name.

All three subelements shown in Example 6-5 are mandatory: the <managed-bean-name> element declares the variable name used to refer to instances of the class; the <managed-bean-class> element declares the fully qualified class name; the <managed-bean-scope> element declares in which scope instances should be placed. The supported scopes values are request, session, application, and the special value none.

When JSF evaluates a value binding expression and encounters a variable that doesn't exist in one of the scopes, it consults the configuration file and creates an instance of the managed bean with a matching name. If the bean scope is declared as something other than none, JSF saves the instance in the specified scope. A bean declared with scope none is created every time the application asks for it, and it's not placed in any scope. This can be useful for beans with properties that have calculated values (e.g., the current time) and never need to be accessed by other application classes.

To recap what's going on in Example 6-4, when the JSP page is rendered for the first time, JSF evaluates the #{reportHandler.from} value binding expression for the first JSF component. It can't find the reportHandler variable in any scope, so it consults the jsf-config.xml file shown in Example 6-5. There, it finds a declaration for the reportHandler bean, so it creates an instance of the bean and saves it in the declared scope (the session scope, in this case). JSF then continues its evaluation of the value binding expression, getting the from property value of the newly created bean and using it as the value for the component. JSF then evaluates the #{reportHandler.to} value binding expression for the second component. Now the reportHandler variable is available in the session scope, so it gets the to property value and uses it for the second component, and so on for all components in the view.

    Previous Section  < Day Day Up >  Next Section