Previous Section  < Day Day Up >  Next Section

8.1 Understanding the JSF Event Model

The JSF event model is based on the event model defined by the JavaBeans specification. In this model, an event is represented by an instance of an event class. An event source object fires an event by calling an event notification method on event listener objects registered to receive the event, passing a reference to the event object as a notification method argument.

Let's look at what this means in more detail. All JSF event classes extend the javax.faces.event.FacesEvent class:

package javax.faces.event;



import java.util.EventObject;

import javax.faces.component.UIComponent;

...



public abstract class FacesEvent extends EventObject {

    public FacesEvent(UIComponent component) {

        super(component);

    }



    public UIComponent getComponent( ) {

        return ((UIComponent) getSource( ));

    }

    ...

}

The FacesEvent class extends the standard Java event superclass java.util.EventObject and has a constructor that takes the UIComponent event source object as an argument. It also implements a type-safe accessor method for the event source object.

When a user clicks a button, it triggers an event represented by the javax.faces.event.ActionEvent class:

package javax.faces.event;



import javax.faces.component.UIComponent;



public class ActionEvent extends FacesEvent {

    public ActionEvent(UIComponent component) {

        super(component);

    }

    ...

}

Other events are represented by similar concrete subclasses, such as the javax.faces.event.ValueChangeEvent, which signals a value change.

Along with the event classes, there are listener interfaces declaring the methods that the event source calls to notify listeners of the event. A listener interface can contain methods for many related events, but for the JSF component events, there's a separate interface per event. Here's the javax.faces.event.ActionListener interface:

package javax.faces.event;



import javax.faces.component.UIComponent;



public interface ActionListener extends FacesListener {

    public void processAction(ActionEvent event) throws AbortProcessingException;

}

The ActionListener interface extends the javax.faces.event.FacesListener interface and defines one method, taking an ActionEvent instance as the single argument.

Classes that want to be informed about events are called event listeners. They declare which events they are interested in by implementing the corresponding listener interfaces. Hence, an event listener that wants to deal with the ActionEvent fired by a command component declares its intent like this:

package com.mycompany.expense.ui;



import javax.faces.event.ActionListener;



public class ReportHandler implements ActionListener {

    ...

    public void processAction(ActionEvent e) throws AbortProcessingException {

        ...

    }

}

To prevent other listeners from seeing an event, all JSF event-processing methods can throw a javax.faces.event.AbortProcessingException. This is rarely needed, but can come in handy when serious problems occur while processing the event. If the event notification method throws this exception, JSF stops the event processing immediately.

Event source classes, like UICommand, declare the type of events they can fire by providing methods for registering and deregistering the corresponding event listeners:

    public void addActionListener(ActionListener listener) {

        addFacesListener(listener);

    }



    public void removeActionListener(ActionListener listener) {

        removeFacesListener(listener);

    }

The methods follow the JavaBeans conventions: the method names are made from the words add and remove followed by the listener interface name, and both methods take an instance of the listener as the single argument.

The addFacesListener() and removeFacesListener() methods called by the registration and deregistration methods are protected methods implemented by UIComponentBase, so that the task of maintaining the listener list doesn't have to be implemented by all subclasses.

When a component notices that a user event has happened, it creates an instance of the corresponding event class and adds it to an event list. Eventually, JSF tells the component to fire the event, i.e., loop through the list of listeners for that event and call the event notification method on each one.

8.1.1 The Request Processing Lifecycle Phases

So far, so good. If you've programmed with GUI frameworks or in other event-driven environments, the JSF event model should look familiar. But as I mentioned in the introduction, the fact that JSF operates with a disconnected client that only occasionally communicates with the server where the application runs requires a few twists to the model.

First of all, the application may declare that the component instances should not be saved on the server between requests, due to memory usage concerns. JSF must then save enough information somewhere (e.g., in the response) to be able to reconstruct the component tree and restore all component state when it receives the next request. In other words, component instances may come and go in a JSF application, as opposed to a GUI application where they remain in memory as long as the application runs.

JSF must also deal with the fact that value changes happen in the client and that the server can't detect the changes until it receives a new request with the new values. One possible work-around is to add client-side scripting to the mix, so that value changes causes an immediate request; unfortunately, that solution can make the application feel sluggish, and it doesn't work at all if the client doesn't support scripting.

Button and link clicks are a bit easier to deal with, because they cause a new request always to be sent. Events corresponding to these user actions can, however, be classified further into events that affect only the user interface (e.g., show the next set of rows in a table or change from a brief to a detailed display) and events that must be processed by backend code (e.g., permanently save the values entered in a form or finalize a reservation request). For user interface events, the backend typically shouldn't be bothered at all. An event that involves the backend, on the other hand, must not be processed until all model properties have been updated with the new values received with the request.

JSF deals with all these concerns by defining a request processing lifecycle with well-defined phases (shown in Figure 8-1), where different activities take place and events fire in an orderly manner at the appropriate time.

Figure 8-1. Request processing lifecycle phases
figs/Jsf_0801.gif

The phases are described in detail in Appendix C, but here's a quick rundown:

  • In the Restore View phase, the components that make up the view are restored either from data in the request or from data saved on the server, as described in Chapter 6.

  • Next, in the Apply Request Values phase, each component in the view looks for its own value in the request and saves it.

  • The phase where the components may validate their new values is appropriately called the Process Validations phase. What happens in this phase, as well as in the Apply Request Values phase, is described in more detail in Chapter 7.

  • Model properties bound to components through value bindings, as described in Chapter 6, are updated with the new values in the Update Model Values phase.

  • When the model properties have been populated with the latest values, event listeners may call backend code to process the new data in the Invoke Application phase.

  • Finally, a response to the request is sent, using the same view or a new one. This happens in the Render Response.

If the user triggered the request by clicking a button or a link, the corresponding UICommand component discovers this in the Apply Request Values phase when it finds a request parameter with a name that matches its ID. It creates an ActionEvent to represent the event, but doesn't notify the listeners immediately, because the rest of the components in the tree may not have picked up their values yet. Instead it adds it to a queue by calling its queueEvent() method.

At the end of the Apply Request Values phase, when all components know about their new values, it's safe to fire an ActionEvent that affects the user interface. JSF asks the UICommand that queued it to notify all listeners by calling its broadcast() method. But as you'll see soon, that's not the whole story: sometimes this event must not be broadcast until the Invoke Application phase.

Value change event handling happens in the Process Validations phase in a similar fashion, by default. A component that discovers that a value in the request is both valid and different from its previous value queues a ValueChangeEvent. Because the user may have changed a number of values since the previous request, it is likely that more than one event is queued in this phase. At the end of the phase, JSF scans the queue and calls the broadcast( ) method on the component that queued a ValueChangeEvent.

A listener processing an event may in turn do things that cause other events to be queued, so JSF continues to scan the queue at the end of a phase until the event queue is empty before moving on to the next phase.

    Previous Section  < Day Day Up >  Next Section