Handling Events in JavaServer Faces, Part 2
by Hans BergstenEditor's note: The JSF model has the same look and feel as the event model used for standalone applications, but because user actions in JSF take place in a client separated from the server, delays occur in the delivery of some types of events until a new connection is established. In last week's part one of this two-part excerpt from JavaServer Faces, author Hans Bergsten provided examples to show how JSF deals with this difference, by using a strict request processing lifecycle. Here in part two, Hans implements event handling for parts of the sample application discussed in part one.
Handling User Interface Events
When the user clicks a button or a link, chances are good that backend code should be asked to do something, like adding a report entry to the current report when the Add button is clicked in the sample application. Occasionally, though, an event affects only the user interface. For instance, clicking a button or changing the value of an input control may expose additional options or display more information.
|
Related Reading JavaServer Pages |
As an example of user interface changes triggered either by a button click or a value change, let's add a feature to the sample application, namely an extendable expense types list. Initially, only the most common expense types are listed, but the user can extend the list with more uncommon choices.
Triggering an Event by Clicking a Button or a Link
In the first version of the extendable expense types feature, I use a button to switch between the standard and the extended list. Figure 8-3 shows what it looks like.

Figure 8-3. The report entry form area with a button for extending the type choices
The JSP page for this version of the entry form area is shown in Example 8-4.
Example 8-4. Entry form area JSP page with a button for extending the type choices (expense/stage3/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:inputText>
<h:message for="date" />
<br>
Type:
<h:selectOneMenu id="type" required="true"
value="#{entryHandler.currentEntry.type}">
<f:selectItems value="#{entryHandler.currentChoices}"/>
</h:selectOneMenu>
<h:commandButton value="Ext/Std" immediate="true"
action="#{entryHandler.toggleTypes}" />
<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:inputText>
<h:message for="amount" />
<br>
<h:commandButton value="Add"
disabled="#{reportHandler.addEntryDisabled}"
action="#{entryHandler.add}" />
</h:form>
<h:messages globalOnly="true" />
<%-- Loop to verify that it works --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<ol>
<c:forEach items="${reportHandler.currentReportEntries}" var="e">
<li>Date: ${e.date}, Type: ${e.type}, Amount: ${e.amount}</li>
</c:forEach>
</ol>
</f:view>
As you can see in Example 8-4, I've added a button with the label "Ext/Std" for toggling between the standard choices list and the extended list. I've also replaced the value binding expression for the entry type list with one that gets the current choices, i.e., with or without the extension choices.
The one thing that's different about the
<h:commandButton> element for the Ext/Std
button compared to the one for the Add button is the
immediate attribute. Setting this attribute to
true means that the ActionEvent
is processed in the Apply Request Values phase and that the
processing then jumps directly to the Render Response phase, without
doing any validation or updating model values.
That's exactly how we want a pure user interface
event to be handled, because we don't want error
messages about missing or invalid values to be shown just because the
user asks for more options.
The action attribute for the
<h:commandButton> action element contains a
method binding expression that points to the toggleTypes(
) method in the entryHandler. Example 8-5 shows how it's implemented in
the corresponding EntryHandler class.
Example 8-5. Additions for extendable types in EntryHandler
package com.mycompany.expense;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Map;
...
public class EntryHandler {
private Map specialTypes;
private List specialChoices;
private boolean includeSpecial;
...
public String toggleTypes( ) {
includeSpecial = !includeSpecial;
return "success";
}
...
The toggleTypes() method simply sets a variable
named includeSpecial to the reverse of its
previous value, alternating between true and
false.
The other changes to the EntryHandler class that
this new feature requires are pretty mundane: we need to configure it
with the list of extension choices and implement a
getCurrentChoices() method that takes the
includeSpecial flag into consideration. Both of
these tasks are very similar to what we did in Chapter 7 for the list of standard choices.
The extension choices are defined in the faces-config.xml file as a new Map property named
specialTypes:
<faces-config>
<managed-bean>
<managed-bean-name>specialTypes</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>Presentation Material</key>
<value>100</value>
</map-entry>
<map-entry>
<key>Software</key>
<value>101</value>
</map-entry>
<map-entry>
<key>Balloons</key>
<value>102</value>
</map-entry>
</map-entries>
</managed-bean>
<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>specialTypes</property-name>
<value>#{specialTypes}</value>
</managed-property>
...
</managed-bean>
...
</faces-config>
No surprises here. The new specialTypes property
is configured exactly like the expenseTypes
property described in Chapter 7.
The third and final addition is a new method for getting the list of
selection items. Note that the <h:selectItems> element in Example 8-4 is bound now to the
currentChoices property instead of the expenseTypeChoices property:
public List getCurrentChoices( ) {
List choices = new ArrayList( );
choices.addAll(getExpenseTypeChoices( ));
if (includeSpecial) {
choices.addAll(getSpecialChoices( ));
}
return choices;
}
}
The getCurrentChoices() method uses the
getExpenseTypeChoices() method described in Chapter 7. If includeExtensions is
true, it calls a similar method to include the
extension types in the list along with the standard types.
Finally, we must implement the setter method for the
specialTypes property and the
getSpecialChoices() method that returns a
List containing SelectItem
instances for the extension types:
public void setSpecialTypes(Map specialTypes) {
this.specialTypes = specialTypes;
}
private List getSpecialChoices( ) {
if (specialChoices == null) {
specialChoices = new ArrayList( );
if (specialTypes != null) {
Iterator i = specialTypes.entrySet( ).iterator( );
while (i.hasNext( )) {
Map.Entry me = (Map.Entry) i.next( );
specialChoices.add(new SelectItem(me.getValue( ),
(String) me.getKey( )));
}
}
}
return specialChoices;
}
This should also look familiar from Chapter 7.
JSF calls the setSpecialTypes() method with the
Map created from the
faces-config.xml managed bean declaration, and
the getSpecialChoices() method creates a
List with SelectItem instances
based on the Map values and returns it.
The effect of all this is that when the user clicks the Ext/Std
button, the default ActionListener associated with
the button handles the event in the Apply Request Values phase
(because the immediate attribute is set to
true) by invoking the toggleTypes(
) method and asking JSF to redisplay the same page without
going through the Process Validations and Update Model Values phases.
When the UISelectOne component for the expense
types list evaluates its value binding in the Render Response phase,
it invokes the getCurrentChoices() method, which
includes or excludes the extension types depending on the value of
the includeSpecial flag.
Pages: 1, 2 |
