Any article discussing AJAX is currently an instant hit. Articles talking about JSF (Java Server Faces) are also pretty hot. So the combination must drive the number of reads through the roof… While many developers are trying to figure out how the AJAX and JSF paradigms can be sensibly merged together, one of the richest JSF implementations on the market – ADF Faces, created by Oracle and donated to the Open Source Apache MyFaces project – is equipped with a lot of on-board AJAX capabilities. In this article I will show you which AJAX tricks ADF Faces has up its sleeves and how we can use them – virtually without any programming.
We will see a Box Office application where we can order Theater Tickets. It has a particularly responsive interface that has the following features
- instant re-calculation of the over-all price of the tickets
- refresh of the list of available shows when I change my type-of-show preferences
- instant validation of the number of tickets I order (as this number is limited for popular shows)
- dynamic enable/disable of the seating item – some shows do not have a seating arrangement
- adopt the thumbnail image shown to the show selected
I believe that this list contains the most valuable uses of the concept of AJAX – which I would define as ‘having the web client communicate with the server without the user being aware and possibly update specific sections of the page based on the response received from the server, all to make for a quicker response to a user’s actions’. ADF Faces does not use the XmlHttpRequest Object, usually associated with AJAX. Does that make this any less AJAX? Not in my book.
Instant calculation of derived values, validation of newly entered values, updates of selection lists and refresh of screen widgets (enable/disable or hide/show) is I believe the bulk of what AJAX can do for data entry driven web applications.
Introducing the Box Office application
Let’s talk for a brief moment about what our application will do. The user can select a show, a number of tickets and a seating arrangement (when available). The application will instantly re-calculate the price of the order, whenever either the show, the number of tickets or the seating arrangement is changed. When the user picks a show without seating arrangement, the seating arrangement radiogroup is immediately disabled.
To help with the selection of the show, there are Categories listed. Using checkboxes, the user can specify which types of shows he is interested in. The list of shows is automatically refreshed whenever a checkbox is checked or unchecked.
For the show the user selects, a thumbnail image is displayed (when available).
Finally, the user can specify the date on which he and his party would like to attend. The Date Chooser is updated immediately with the Play List for the selected show and any date entered is subject to immediate validation against the schedule of the show.
The picture above indicates which parts of this user interface dynamically (AJAX-style) respond to user actions.
Before we go into how each of these dynamic actions was implemented in ADF Faces, let’s first inspect the application in general.
The Box Office Application
The application is created using Oracle JDeveloper 10.1.3. There is no backing database. It application uses a single JSPX file – more on that later – and three classes: Show – the Model bean that represents a theater production with properties like title, the opening and closing date, the category, the base ticket price and a (reference to a) thumbnail image. The BookingBean is the backing bean for the UI components’ value-properties, used by the application to hold the values entered by the user. It also has the logic for calculating the total price. The BoxOfficeBean is something like a Controller in this application. It instantiates a collection of shows, performs validations (well, actually there is just the validation of the number of tickets) and it manipulates the contents of the List of Shows whenever the Categories multi-checkbox item is modified.
The JSF page in our application – BoxOffice.jspx – imports the tag-libraries for the default (RI) Core and HTML JSF tag-libraries as well as the Core and HTML ADF Faces tag-libraries:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:afh="http://xmlns.oracle.com/adf/faces/html"
xmlns:af="http://xmlns.oracle.com/adf/faces">
The page was created in JDeveloper from the New Gallery, selecting the tag-libraries in the wizard. No coding was required.
Using the Component Palette, the page was created by simply dragging and dropping the relevant components. The main layout-structure was created using the Panel Page with facets for Branding, Menu (not used), Title, Copyright etc. The logo was added to the page by simply dragging the logoamis.gif file onto the branding facet in our Panel Page!
An ordinary Panel Grid was used to create the overall structure with two columns: one for the field prompts and the other for the fields themselves. The page in design time – the ADF Faces WYSIWYG editor in JDeveloper – looks like this:
Note that we can switch to source view at any time and directly type the XML, rather than use drag & drop, wizards and property editors. If we feel we have to.
Implementing AJAX Features with ADF Faces
I promised you a discussion of four major AJAX-style bevhaviors and their implementation in ADF Faces. So here we go with Calculation, Validation, List Refresh, Component Manipulation (like disabling/enabling).
Calculation of Total Price
The total price of the fictional Box Office order is refreshed whenever the user changes either the Show, the Seating or the Number of Tickets. The total price UI Component is defined like this:
<af:panelLabelAndMessage label="Total Price"
inlineStyle="font-weight:bold;">
<af:outputText value="#{bookingBean.totalPrice}"
partialTriggers="f1:pc f1:show f1:seating "/>
</af:panelLabelAndMessage>
We can see that its value is bound to the bookingBean’s totalPrice property. So that is where it gets its value from. That by itself does not get us any AJAX-style, immediate refresh functionality. We need to additional things for that. The first is also in the snippet above: the partialTriggers property. We have specified that we want ADF Faces to refresh – client side, DHTML based refresh – whenever the three components specified with f1:pc, f1:show and f1:seating have changed. These three, as will not be surprise, are respectively the Number of Tickets, the Show and the Seating Arrangement input items.
The final piece in the puzzle is found with these three components. For each of them, we have set the autoSubmit property to true. See
here the examle of the Number of Tickets element:
<af:outputLabel id="partyLabel" for="pc" value="Number of Tickets"/>
<af:inputText value="#{bookingBean.partyCount}"
autoSubmit="true"
id="pc"
validator="#{boxOfficeBean.validatePartyCount}">
This tells the ADF Faces framework that whenever the value of this inputText is changed by the user – onChange JavaScript event fires – there should be an Asynchronous post of all values in the current input-form. The ADF Faces request life-cycle is performed and the results are sent back to the client. This is all done using a – hidden – IFRAME. ADF Faces contains JavaScript libraries that can interpret the contents received in the IFRAME and process them into the page as partial page updates using DHTML (JavaScript DOM manipulation).
This means: if user changes either the selected Show, the Seating or the Number of Tickets, an AJAX-like request is sent to the server, all form values are applied, validation is performed and all attributes bound to backing bean properties are recalculated and a fresh (partial) page is rendered and sent back to the server. For the Total Price this means that the bookingBean.getTotalPrice() method is called:
public double getTotalPrice() {
double seatingFactor = 1;
if (seating !=null) {
if (seating.equalsIgnoreCase("orch"))
seatingFactor = 2.0;
if (seating.equalsIgnoreCase("mez"))
seatingFactor = 1.3;
if (seating.equalsIgnoreCase("sro"))
seatingFactor = 0.5;
}
return (show==null?0:show.getBaseTicketPrice()) * partyCount * seatingFactor;
}
When the partial page update is performed client side, the total price is refreshed from whatever value was produced by the getTotalPrice() method.
All in all the functionality is very much like coding an onChange JavaScript event trigger that performs the above logic. However, that JavaScript function would need to have knowledge of the base ticket price for all Shows in the entire show collection. In addition, accessing the various form fields client side is not as simple as simply accessing bean properties. To cut a long story short, all we had to do to implement this dynamic recalculation of the Total Price was setting three autoSubmit properties to true, setting the partialTriggers attribute on the totalPrice item and coding the getTotalPrice() method – which we needed anyway.
Synchronize the Thumbnail Image
Well, they do not get much easier than the thumbnails. Each Show has a property thumbnailUrl that is initialized with the URL where a fitting image for the show can be found. The image is included in the page using the objectImage element. We bind its source property to the thumbnailUrl property in the show currently selected in the bookingBean:
<af:objectImage id="showThumbnail" source="#{bookingBean.show.thumbnailUrl}"
height="150" width="100"
partialTriggers="f1:show"/>
As you might have guessed by now, the partialTriggers property specifies that this objectImage should be partially refreshed whenever the f1:show element is changed (so whenever a new show is selected). We have seen already with the total price calculation that we also need the show element to set its autoSubmit property to true. To ensure that an asynchronous , background request is sent with the newly selected show among the values submitted.
As a result, selecting a new show in the list will immediately refresh the thumbnail image:
and after selecting The Woman in Black (just clicking on it):
Instant Validation of Number of Tickets and Date of Performance
Validation is obviously a core feature of Java Server Faces. Basic validation is available from a small set of standard validators. ADF Faces extends this set with some additional validators – like validateDateTimeRange validator and Regular Expression validator – and enhances the validators to also perform Client Side validation. This means that without communication with the server a value is validated in JavaScript code. When a validation error is encountered, an appropriate alert is shown.
Let’s start simple. The Box Office does never allow more than 8 tickets to be ordered (to discourage the black market). We have specified so in the page definition:
<af:inputText value="#{bookingBean.partyCount}" autoSubmit="true"
id="pc"
validator="#{boxOfficeBean.validatePartyCount}">
<f:validateLongRange maximum="8" minimum="1"/>
</af:inputText>
If the user asks for more than 8 tickets, as soon as he tries to leave the field validation is done and the standard ADF Faces message is shown:
However, our validation requirements are a little more subtle than this. Each show can have its own maximum number of tickets that can be acquired in a single transaction. So in addition to the static client side validation you see here, we have set up a validator – the method validatePartyCount in the boxOfficeBean. This method is shown here:
public void validatePartyCount(FacesContext facesContext,
UIComponent uiComponent, Object object) {
Object submittedShow = getSelectOneShowList().getValue();
if (submittedShow!=null) {
Short maxParty = ((Show)submittedShow).getMaxParty();
if ((Integer)object> maxParty ) {
((CoreInputText)uiComponent).setValid(false);
FacesMessage message = new FacesMessage("The maximum of tickets for this show is unfortunately limited to "+maxParty+"!");
facesContext.addMessage(uiComponent.getClientId(facesContext), message);
}
}
}
You can tell that this validator method inspects the value of the Number of Tickets element – (Integer)object – and compares it to the maximum number of tickets specified for this show – ((Show)getSelectOneShowList().getValue()).getMaxParty(). Note that we make use of the getSelectOneShowList method that holds the SelectOneList element that was bound to it from the binding property:
<af:selectOneListbox tip="Select the show of your choice!"
... binding="#{boxOfficeBean.selectOneShowList}">
If the number of tickets desired by the user exceeds the maximum number allowed for this particular show, a new message is added to the FacesContext.
In order to ensure that this message is actually presented to the end-user as part of the dynamic page refresh, we have to set the partialTriggers property for the messages component. This tells ADF Faces to refresh the messages container when a partial page refresh is performed as a result of changes in either the show, the pc (party count or Number of Tickets) or showDate element:
<af:messages id="msg" partialTriggers="f1:show f1:pc f1:showDate "/>
If we change either the Show when we have already set a too high number of tickets or change the number of tickets to a higher number than allowed for the currently selected show, the Validator will kick in and present us with an error message:
Note: we could also have made the client side validation a little more dynamic by binding the maximum property of the validateLongRange validator:
<f:validateLongRange maximum="#{boxOfficeBean.show.getMaxParty}" minimum="1"/>
However, this does not give us the opportunity to create tailored error messages. Which we did not really do in this example either…
We have one final piece of validation up our sleeves: date validation. Each one of the shows presented in this application has a specific opening and closing date. That means two things for the application
- the date chooser element – the little calendar shown behind the Date of Performance element – should only present days in the valid range for the currently selected show
- the dates entered by the user directly into the Date of Performance element must be validated to be in the period during which the show is running
The first feat is accomplished very easily by binding the maxValue and minValue attributes of the chooseDate element to the opening and closing date properties of the currently selected show, as available from thye bookingBean:
<af:chooseDate id="chooseDate1"
maxValue="#{bookingBean.show.closing}"
partialTriggers="f1:show"
minValue="#{bookingBean.show.opening}"/>
To make sure that the dateChooser is refreshed whenever a new show is selected, we have set the partialTriggers property to tie in with the f1:show element. The result:
Change the selected show and watch what happens to the Date Chooser:
The second requirement – validating the date entered by the user – is even easier. We can use the standard ADF Faces validateDateTimeRange validator:
<af:selectInputDate id="showDate" chooseId="chooseDate1"
value="#{bookingBean.showDate}"
partialTriggers="f1:show " autoSubmit="true"
tip="Specify the date on which you would like to attend the show">
<af:convertDateTime/>
<af:validateDateTimeRange maximumMessageDetail="This show does not run after {2}"
maximum="#{bookingBean.show.closing}"
minimum="#{bookingBean.show.opening}"
minimumMessageDetail="This show does not open until {2}"/>
...
The validation is performed whenever we change the show selection or the value for Date of Performance. However, I do not always yet get the pretty error messages I feel I deserve having followed the ADF Faces guidelines. What I get is the following:
The detailed pretty message is only displayed after changing the show, not after entering a new date value. I may have set the partialTriggers property at the wrong level… Perhaps I should only allow the users to set the Date of Performance through the Date Chooser; then they could never run into this validation failure.
List Refresh
A frequent example in discussions of AJAX is the dynamic adjustment of a list of allowable values. In our Box Office application, we have a SelectManyCheckbox element that display four categories of Theater Experiences: Play, Musical, Ballet and Opera. The list of shows we present to the user must contain only those shows that correspond to the categories currently selected by the user. So the SelectOneList element that displays the Shows must be updated whenever the selection of categories change.
This is much easier than it may seem to you. Let’s first look at the UI Components in our page definition:
<af:outputLabel for="cat" value="Category of Shows"/>
<af:selectManyCheckbox value="#{bookingBean.categories}"
tip="The category of shows"
layout="vertical" autoSubmit="true"
id="cat">
<af:selectItem label="Play" value="pla"/>
<af:selectItem label="Musical" value="mus"/>
<af:selectItem label="Ballet" value="bal"/>
<af:selectItem label="Opera" value="ope"/>
</af:selectManyCheckbox>
<af:outputLabel id="showLabel" for="pgShow" value="Show"/>
<af:panelGroup id="pgShow" partialTriggers="f1:show"
layout="horizontal">
<af:selectOneListbox tip="Select the show of your choice!"
size="5" autoSubmit="true"
value="#{bookingBean.show}" id="show"
partialTriggers="f1:cat"
binding="#{boxOfficeBean.selectOneShowList}">
<f:selectItems value="#{boxOfficeBean.showSelectItems}"/>
</af:selectOneListbox>
We can tell from autoSubmit="true" on the cat element that changing the selection of Categories will initiate a partial asynchronous request cycle. The partialTriggers property on the show element includes f1:cat; this specifies that the list of Shows is to be refresh whenever a Partial Page refresh is performed as a result of a change in Category Selection.
The selectItems element that provides the values displayed in the Show-list is bound to the getShowSelectItems() method on the boxOfficeBean. So clearly that particular method should select and return the shows that satisfy the category condition.
public SelectItem[] getShowSelectItems() {
String lastCategory="";
SelectItem[] items = new SelectItem[ shows.size()+10];
int i=0;
for (Show show:this.shows) {
if (fitsCategory(show.getCategory(), this.bookingBean.getCategories()))
items[i++] = new SelectItem(show,show.getTitle());
}
return items;
}
private boolean fitsCategory(String cat, String[] cats) {
if (cats!=null) {
Collection<String> labels = Arrays.asList(cats);
return labels.contains(cat);
}
else return false;
}
The getShowSelectItems() method prepares a SelectItem[] array, iterates through the entire set of shows available in the BoxOfficeBean from the shows member variable and has the method fitsCategory determine, based on the category of the show compared to the set of categories currently selected by the user, whether or not to include the show in the set that is returned from the method. See the example below:
Now if we add Ballet and Opera to our selection, the Show list is refreshed instantly:
Component Manipulation – Enable/Disable the Seating Arrangement
Not all shows have a seating arrangement. Most Operas do while most plays and musicals do not. Our user interface should respond accordingly: if the user p
icks a show withou
t a seating arrangement, we will disable the Seating Arrangement element. If the show does have one, we will enable the element. Note: we could also make it disappear or appear. We chose not to as it makes for a less consistent, less appealing experience. There is no technical limitation.
Implementing this dynamic switching on and off is – as you are used to by now – amazingly simple and largely declarative. The disabled property of the selectOneRadio element is bound to the hasSeatingArrangement method in the Show bean returned by the gertShow() method in the bookingBean.
<af:selectOneRadio value="#{bookingBean.seating}"
autoSubmit="true" id="seating"
partialTriggers="f1:show "
disabled="#{!bookingBean.show.hasSeatingArrangement}">
<af:selectItem label="Orchestra" value="orch"/>
<af:selectItem label="Mezzanine" value="mez"/>
<af:selectItem label="Balcony" value="bal"/>
<af:selectItem label="Standing Room" value="sro"/>
</af:selectOneRadio>
To make sure the disabled property is re-evaluated during partial page refresh, we have to set the partialTriggers property of the Seating Arrangement element to include the f1:show element. This means: if another show is selected, refresh the Seating Arrangement element.
Resources
Download the JDeveloper 10.1.3 Project with our three beans and the Box Office jspx page: BoxOffice.zip. Simply download, extract and open the TicketService.jpr file in your JDeveloper 10.1.3 instance. Note1: you may have to copy adf-faces-impl.jar to the public_html\WEB-INF\lib directory. You can find this file in your JDeveloper 10.1.3 installation: JDEV_HOME\jlib . Note2: you should be able to run the application in other IDEs as well, as long as you have access to the ADF Faces libraries.
Your app is very usefull for me.
All work OK!
Thanks!
Lucas, very interesting JSF-Ajax-article. I will try it, soon.
Lucas, thanks for the very interesting article. I will try it soon…
Hi guys, after being working with jsf (ADF faces) for 6 months, i was surprised by this article, becouse i belived that this couldn’t work well. Efectively, i have reproduced this example (i hope) and it doesn’t work well, if you type a wrong value (evaluated as invalid by the validator) in the pc input, and then click several times in the category selector, the show selector is not refreshed. Then, when you fix the value of the pc input, the error message dissapear but you have an incoherent view, between cat and shows, becouse the shows selector hasn’t been refreshed with the cat value.
Am i making a mistake in the code? or is this missfunction “normal”, due to the JSF phases? Best Regard. Alberto.
Hi Lucas,
Very nice article , especially when I am looking for solutions to use AJAX with ADF-Faces.
Another solution I suggest is Ajax4jsf , I like its page wide Ajax function (instead of component wide),just like PPR did. Besides, it also provide some attributes like
RequestDelay , EventsQueue …
https://ajax4jsf.dev.java.net/nonav/ajax/ajax-jsf/download.html
best regards,
Eron
Geetha,
The functionality described in this article is already available right now. It is all part of ADF Faces that ships with Oracle 10g JDeveloper release 10.1.3, available from OTN. They are also part of the Apache MyFaces Trinidad project that you find through http://myfaces.apache.org/.
best regards,
Lucas
This article really excites everyone who are already using ADF faces and want to migrate to AJAX. Could you please let me know when will the production ready version be released?
Yes, you would have specify the entire chain – it is the only way of specifying which component is triggering you. The entire chain defines the ID of the component.
Lucas
In your example, how do you determine the component id, such as f1:show? Specifically, if you have a deep nexted mark up, do you need to define the id attribute for every component in the chain?
thanks
Bill
Could you send me your code so I can have a look at what you are exactly trying to do? (jellema@amis.nl).
Lucas,
This is an excellent article for PPL who are trying ro implement AJAX with ADF faces.
I was wondering if you can help me with this bug.
I am not able to disable/enable a text input upon radio button toggle. But User cannot enter a value into af:inputtext when radio button was un-checked and was able to enter a value when radio is checked.
But I cannot disable/enable like you did the radio button when show was selected from the list.
any ideas?
Thanks
Sagar
Nice reference by Shay Shmeltzer: http://blogs.oracle.com/shay/discuss/msgReader$151
“From all the talks at Javaone about AJAX you might end up thinking that you should pick up a Javascript book ASAP, if you want to survive the next wave of Web applications. But this is where JSF comes to your rescue. One of the nice things about the JSF components concept is that you don’t actually need to write Javascript – you just need to pick up components that will do the work for you.
In this blog entry Lucas Jellema shows you how you can achieve several AJAX capabilities using the ADF Faces set of JSF components and zero lines of Javascript.
Worth a read…
“
Good to hear from you again Didier! Thanks for the compliment – and the reference on your blog.
Lucas
Excellent article Lucas. Always interesting to read you.
Thanks,
Didier.