Top Banner
GETTING THE MOST AJAX OUT OF ADF FACES: DEVELOPING REALLY RICH WEB APPLICATIONS Lucas Jellema, AMIS Web Applications have recently started their second life: Web 2.0 it is called. And AJAX is the hype du jour. However, the beef of Web 2.0 is more than just another hype. It is about creating Web Applications with the richness of Desktop applications. With fast, interactive responsiveness, functionally rich widgets and dynamically updated, visually appealing pages. Typically, this type of User Interface either requires expensive libraries and/or lots of messy hand-coding of JavaScript. However, with ADF Faces the rich interaction comes as part of the framework. With mostly declarative settings and little server side Java programming, we can develop the rich functionality that characterizes Web 2.0 applications. ADF Faces provides the most valuable uses - Instant calculation of derived values, validation of newly entered values, updates of selection lists and refresh of screen widgets (enable/disable or hide/show) - 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’. This paper explains how AJAX(-like) features can be used by ADF Faces developers to add functional richness, dynamic user interfaces and high interactivity to their applications - largely declaratively without any programming. It discusses the way AJAX has been implemented in ADF Faces and how to make the most of it. It will also briefly discuss the rich UI components that have very recently been released by Oracle in the JDeveloper 11g Technical Preview release - to add the latest generation of widgets such as the Accordion, client-side drag & drop and spreadsheet-style data grids. www.odtug.com 1 ODTUG Kaleidoscope 2007
14
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: jellema2_ajax.doc

GETTING THE MOST AJAX OUT OF ADF FACES: DEVELOPING REALLY RICH WEB APPLICATIONSLucas Jellema, AMIS

Web Applications have recently started their second life: Web 2.0 it is called. And AJAX is the hype du jour. However, the beef of Web 2.0 is more than just another hype. It is about creating Web Applications with the richness of Desktop applications. With fast, interactive responsiveness, functionally rich widgets and dynamically updated, visually appealing pages. Typically, this type of User Interface either requires expensive libraries and/or lots of messy hand-coding of JavaScript. However, with ADF Faces the rich interaction comes as part of the framework. With mostly declarative settings and little server side Java programming, we can develop the rich functionality that characterizes Web 2.0 applications.

ADF Faces provides the most valuable uses - Instant calculation of derived values, validation of newly entered values, updates of selection lists and refresh of screen widgets (enable/disable or hide/show) - 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’.

This paper explains how AJAX(-like) features can be used by ADF Faces developers to add functional richness, dynamic user interfaces and high interactivity to their applications - largely declaratively without any programming. It discusses the way AJAX has been implemented in ADF Faces and how to make the most of it. It will also briefly discuss the rich UI components that have very recently been released by Oracle in the JDeveloper 11g Technical Preview release - to add the latest generation of widgets such as the Accordion, client-side drag & drop and spreadsheet-style data grids.

Figure 1- Some of the built-in AJAX-style facilities in ADF Faces components

True AJAX and ADF FacesThe essential part of AJAX is the asynchronous communication between browser and server – unnoticed by the user, no blank browser page, no hourglass, no frozen application. In recent years, this has usually been achieved through the

www.odtug.com 1 ODTUG Kaleidoscope 2007

Page 2: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

JavaScript XmlHttpRequest Object, that allows the programmer to write code to send requests from the browser to the server and handle the response, all in the background.

The AJAX implementation in ADF Faces is different. Instead of the programmatic, JavaScript based interaction, ADF Faces’partial page rendering uses an IFRAME to submit requests to the browser. One added benefit from this robust mechanism is the ability to do upload of files as part of an AJAX request. Some suggest that this mechanism makes it less AJAX somehow, and even Oracle staff seem to be somewhat hesitant to use the term for ADF Faces. I have no such qualms, as ADF Faces Partial Page Rendering or Refresh (PPR) as it typically dupes in the documentation gives us what we want from AJAX: dynamic update of the page as a result of non-blocking, invisible browser-server interaction.

Built-in AJAX capabilitiesSome of the more advanced ADF Faces components have built-in AJAX behavior, that we get without any effort at all. Typical examples are the Table that sports sortable columns and record pagination functionality, based on the PPR mechanism, the Tree with PPR-based expand and collapse of nodes and the showOneTab component that has dynamic switching between tabs. The AJAX character of these features is that all these operations are initiated by the user activating (clicking) a control element and the application responding with a local refresh of the page – with no full page refresh of hourglass block of the browser. When you sort the records in the table for example, only the contents of the table is refreshed, nothing else on the page is repainted.

How to leverage the ADF Faces PPR mechanismThe built-in AJAX features are a very nice beginning. But wait, there is much more! ADF Faces page developers can make use of the same mechanism to add any AJAX style behavior, such as Validation, Update of selection elements (list, radiobuttons, dropdownlist), Hide/Show or Enable/disable elements and add components to page, change UI properties like prompt, hint, CSS style, length.

Figure 2 - The ADF Faces PPR cycle

The basic mechanism is very simple.

www.odtug.com 2 ODTUG Kaleidoscope 2007

Page 3: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

1. Many components such as buttons and links as well as all input elements can trigger a PPR cycle. Action components do so when they are clicked on, input elements when their value changes (which in the case of checkboxes and radiobuttons is of course also when they are clicked). Component will initiate a PPR cycle when clicked or changed if they have their autoSubmit attribute set to true. For action components the partialSubmit attribute is used.

2. The PPR cycle uses the IFRAME to do a submit of an HTML Form that contains all values that are in the form that contains the element from which the PPR was triggered.

3. On the server, the normal JSF lifecycle is executed, including conversions, validations and update of the model.

4. The normal render response is returned to the browser.

5. JavaScript code in the IFRAME processes the response. It is not rendered directly in the browser, as would be the case with a non-PPR request-cycle. Instead, the code determines which component triggered the PPR request – the element with id=”id1” in figure 2. Now the partial page update is performed: only those components in the page that have their partialTriggers attribute set to include the id value of the component where the PPR request started will be refreshed.

6. In figure 2, that means that only the dropdown list element will be refreshed, for example with a list of allowable values inspired by the value just entered in the id1 field.

In short, all the developer has to do to put the AJAX wheels in motion, is: assign autoSubmit=true and an id value to all components that can trigger a PPR cycle and add the id value to all components that should be dynamically refreshed in that PPR cycle. Let’s now take a look at some examples.

And …. action! Your first AJAX steps Now that you have consumed some of the theory, it’s time do see it in action. The first ADF Faces page we will look at is too simple for words: the user can enter his date of birth, through the ADF Faces selectDate component and the page will show his age. We use PPR to show the age immediately after selecting the date, without the user submitting the page.

Figure 3 - After entering the Date of Birth, the age is immediately recalculated and displayed using PPR

The essential part of the source for this page is:

<af:selectInputDate label="Date of Birth" value="#{Customer.dateOfBirth}" id="dob" autoSubmit="true"/> <af:inputText label="Your current age" value="#{Customer.age}" partialTriggers="dob" readOnly="true"/>

The selectInputDate component has its autoSubmit attribute set to true, meaning that whenever the value in this item changes, a Partial Page Refresh cycle should be triggered. Its id is explicitly defined as dob. The element that displays the age has its partialTriggers attribute set to dob, meaning that this item should be refreshed in any PPR cycle triggered by the dob item. These two components have their value attributes bound to the Customer managed bean. This bean is implemented like this:

package nl.amis.crm.model;

import java.util.Date;

public class Customer {

www.odtug.com 3 ODTUG Kaleidoscope 2007

Page 4: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

private Date dateOfBirth; public Customer() { }

public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; }

public Date getDateOfBirth() { return dateOfBirth; } public Short getAge() { if (dateOfBirth!=null) { int today = (int)(new Date().getTime()/3600000)/24; int dob = (int)(dateOfBirth.getTime()/3600000)/24; return new Short((short)((today - dob)/365)); // to crude, no leap years! } return null; }}

Displaying a Happy Birthday greetingIt would be a nice touch to display a Happy Birthday wish if we find that our customer is celebrating her birthday. First we add a method to the Customer bean that tells us whether or not it is that time of the year:

public Boolean getBirthday() { if (dateOfBirth!=null) { return getFormattedDate(dateOfBirth).equalsIgnoreCase(getFormattedDate(new Date())); } return false; } private String getFormattedDate(Date date) { return new SimpleDateFormat("MM/dd").format(date); // we only need day and month }

Then we add an objectImage to our page, for an exuberant birthday illustration:

<af:objectImage height="#{Customer.birthday?360:0}" source="candles-happy-birthday.jpg" partialTriggers="dob"/>

Figure 4 - The birthday cake is only shown when today is the customer's birthday

www.odtug.com 4 ODTUG Kaleidoscope 2007

Page 5: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

Note that is element too has its partialTriggers attribute set to dob, meaning it will be refreshed when the dob element has started a PPR cycle. Finally, look at the so called ternary expression for the height attribute. It can be read as: If the Customer’s birthday property is true (meaning that today is her birthday) then the value is 360, else the value is 0. You see that not just the value attribute can be expressed using dynamic EL expression and refreshed during a PPR cycle, in this case we do the same for the height attribute. Now when we enter a Date of Birth with today’s day and month, we see a nice image.

THE RENDERED ATTRIBUTE

You may and probably should wonder why I have used the height attribute to dynamically hide or display the image. Why not use rendered, the attribute typically used to determine whether an element is visible in the page? Excellent question! The rendered attribute actually does a little more than determine whether an element is visible in the page: it determines whether an element gets rendered to the browser. That means that when rendered is false, the element will not be in the browser at all. The PPR cycle will do a dynamic refresh of all elements whose partialTriggers attribute refers to the element that triggered the PPR. However, if the rendered attribute was false, the element will not be in the page! That means the PPR processor will not find the element and therefore cannot refresh it!

Does this mean that we always have to resort to tricks like using the height attribute to dynamically show and hide elements in a PPR cycle? Well, a more straightforward workaround is the following: wrap the element whose rendered attribute you want to refresh in a PPR cycle inside a parent that is always rendered (though perhaps not visible). When PPR happens, the parent element is refreshed and may display children that were not there before. In our birthday example, we can use:

<af:panelGroup partialTriggers="dob"> <af:objectImage rendered="#{Customer.birthday}" source="candles-happy-birthday.jpg" /></af:panelGroup>

Here we have wrapped the ObjectImage inside a PanelGroup. This PanelGroup is always rendered, though it is not visible as it has no visual properties of its own. The PanelGroup will be refreshed whenever the Date of Birth element changes and fires a PPR cycle. The ObjectImage no longer needs the partialTriggers attribute, nor does it need a dynamic height attribute.

Instant Conversion allowing for short cut data entry and auto-completeNow that we have the ability to instantly process the data entered into a field through the PPR cycle, we can easily implement a simple form of auto-complete. Let’s add a country field to our customer page. It’s a simple inputText where the user can enter the country of a customer. However, we will provide a short-cut for our end users: they can simply type the two letter ISO country code – for example us, uk, nl and de – and have the application turn that entry into the full country name. We need a CountryConverter – that turns the two letter code into the full name -, add that converter to the country field, set autoSubmit to true – to initiate the PPR cycle that will also activate the CountryConverter and include the id of the country field in its own partialTriggers attribute:

<af:inputText id="country" label="Country" partialTriggers="country" autoSubmit="true"> <f:converter converterId="CountryConverter"/></af:inputText>

The key elements of the CountryConverter class are:

public class CountryConverter implements Converter {

Map countries = new HashMap(10); public CountryConverter() { countries.put("us","United States of America"); countries.put("nl","The Netherlands"); // more countries } public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String string) { String country = string; if (country.length()==2 && countries.containsKey(country)) { country= (String)countries.get(country); } return country; }

public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object object) { return object.toString();

www.odtug.com 5 ODTUG Kaleidoscope 2007

Page 6: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

}}

In order to use this Converter, we have to register it in the faces-config.xml file:

<converter> <converter-id>CountryConverter</converter-id> <converter-class>nl.amis.crm.model.CountryConverter</converter-class></converter>

Now when we type us in the Country field and tab out of the field, the PPR cycle kicks in (in the background) and auto-completes the field to United States of America.

Figure 5 - Instant Converter action provides Auto-Complete behavior

Validation and Error MessagesOne popular usage of PPR is providing instant feedback from server side validations. Since a PPR cycle performs the full Java Server Faces lifecycle, including Conversions, Validations and Update Model, we can easily have validations perform as soon as the value of a field is changed. Error messages will be produced to the FacesContext and can be displayed in the browser.

Suppose we have a custom validation that prevents our customers from entering a date of birth that is a Friday the Thirteenth:

public void dateOfBirthValidator(FacesContext facesContext, UIComponent uiComponent, Object object) { if ("Fri/13".equalsIgnoreCase(new SimpleDateFormat("E/dd").format((Date)object))) { ((CoreSelectInputDate)uiComponent).setValid(false); FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR , "Unfortunately we cannot accept customers born on Friday the 13th!", "Wrong date"); facesContext.addMessage(uiComponent.getClientId(facesContext), message); } }

To apply this validator, we have to specify it in the selectInputDate component:

<af:selectInputDate label="Date of Birth" autoSubmit="true" value="#{Customer.dateOfBirth}" id="dob" validator="#{Customer.dateOfBirthValidator}"/>

And of course we need to add the Messages component to our page, in order to display the error messages added to the FacesContext by Validators and others. Now when we run the application and selecting a date that clearly is a Friday the 13 th, we are greeted with the sad information that we cannot be accepted as a customer with that doomed date of birth. Surprisingly enough – to me at least – we do not have to specify the partialTriggers attribute for the af:messages component: it seems to automatically be refreshed in any PPR cycle.

www.odtug.com 6 ODTUG Kaleidoscope 2007

Page 7: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

Figure 6 - Instant field validation using a server side validator

Auto-Refreshing the product listA frequent use of AJAX is to adjust a list of allowable values in a dropdown list or other selection element when some value

is set. For example the filtering of the list of cities when a country or state is selected. Or the refresh of the list of dished when the dietary options have been set. In our simple sample, we will display a list of products the customer can select to order. We

will adjust the products listed based on the age of the customer.

Figure 7 - The Product List gets updated when the age of the customer is modified

You probably know the drill by now: the Date of Birth component is set to autoSubmit=true and the list element displaying the products must have the DOB in its partialTriggers attribute. Finally, the method returning the allowable values to the ProductList should use the Date of Birth to determine the applicable set of products.

The code for synchronizing this product list with the customer’s age is quite straightforward:

<af:panelLabelAndMessage label="Products"> <af:panelGroup layout="horizontal"> <af:selectOneListbox id="prodlist" autoSubmit="true" partialTriggers="dob" value="#{ProductManager.product}"> <f:selectItems value="#{ProductManager.productSelectItems}"/> </af:selectOneListbox> <af:objectImage partialTriggers="prodlist"source="#{ProductManager.product.image}" height="135"/> </af:panelGroup></af:panelLabelAndMessage>

Thrown in for good measure is the objectImage component that shows a thumbnail image for the currently selected product. It is partially triggered by the prodlist, that in turn has its autoSubmit attribute set to true.

www.odtug.com 7 ODTUG Kaleidoscope 2007

Page 8: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

Figure 8 - The thumbnail image is updated when a product is selected

Polling for changesThe ADF Faces poll component opens up some additional options. So far, all PPR cycles have been triggered by a user action. Either a change by the user of a value in a field or the activation of a button or link. However, what if we want an event on the server to trigger a page refresh? Can we somehow perform server push instead of browser pull? Well, the brutally honest answer would have to be no. We cannot implement real server push. What we can do however, is instruct the browser to allow the server at regular times to send changes to the browser. The component we can use for this, is the Poll component. It is incredibly simple. Just set the id and the interval. The latter specifies the time in milliseconds between successive PPR cycles.

<af:poll interval="5000" id="timer"/>

Every time the poll is triggered, the PPR cycle is performed. It can consult web services, database tables, rss feeds or any external sources of information that may provide data that can be used to update the page. Every component that wants to be refreshed after the poll event, has to add the poll’s id to the partialTriggers attribute. Here is an example:

We will present new customers with great discount offer, valid for no longer than 10 minutes. However, due to legal technicalities, we can only do so for customers over 18 years of age, living in America. As soon as we have confirmation of their age and country, the offer is presented on our page. A progress indicator is shown as well as a digital clock, both counting off the ten minutes. Clock and progress bar are refresh every 5 seconds.

Figure 9 - Poll component trigger automatic 'server push' update of the page

The code required for this functionality is pretty straightforward.

www.odtug.com 8 ODTUG Kaleidoscope 2007

Page 9: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

<af:panelGroup partialTriggers="country dob timer"> <af:panelBox rendered="#{Customer.eligible}" partialTriggers="timer"> <af:outputText value="Great Offer especially for you!!"/> <af:goLink text="Click here to order this fantastic offer." destination="http://www.amis.nl"/> <af:progressIndicator partialTriggers="timer" value="#{Customer.countdownModel}"/> <af:outputText id=”countdown” value="Your offer is valid for only #{Customer.countdownModel.remainingFormatMinSec}"/> <af:poll interval="5000" id="timer"/> </af:panelBox></af:panelGroup>

The PanelBox component that displays the great exclusive offer is only rendered when the customer is eligible. This will be the case as soon as Date of Birth and Country meet the conditions. The parent PanelGroup that contains the PanelBox is partially triggered by date of birth and country – remember that the rendered attribute is the only one that can not be dealt with by PPR directly, it must happen via the parent component. The progress indicator as well as the countdown have the poll component in their partialTriggers attribute: every time the poll event is executed, they will be refreshed.

USING THE POLL TO IMPLEMENT AN AUTOMATIC BACKUP

You may have encountered web applications such as Google Mail or Confluence Wiki that make automatic backups of the text you are editing. That is: these applications automatically save your work at regular intervals. If you lose the session accidentally, for example by one of your children helpfully closing the browser, you can still recover most your work from that automatic backup. The ADF Faces Poll component would allow you to implement something similar. Whenever the Poll fires, the entire form it is part of gets submitted and all not yet formally submitted values are posted to the server.

Advanced: Programmatically adding partial targetsIt is easy to add a triggering component to the partialTriggers attribute of the element we want to have refreshed. That is: as long as we know the id value for that component. However, there are situations where that id is unknown, not just because we forgot to explicitly assign one, but also because sometimes we cannot assign an id value explicitly, as is the case for example with tables. Suppose we have a page that display all our customers in a table layout. We would like to show the average age of all our customers in the footer for this table. Whenever we change one of the dates of birth, this average age indication should be refreshed, so its partialTriggers attribute of the column footer should refer to the cells that contain the dates of birth. However, these are only rendered at run-time, one for each record in the table’s underlying collection.

Figure 10 - Average Age is refreshed whenever a Date of Birth is changed, thanks to programmatic setting of partial targets

To still have the table refreshed – because we can only refresh the footer by refreshing the table itself – we can make use of a nifty advanced feature: setting a component as a target of a PPR cycle, even if the component that triggered the PPR cycle is not in the partialTriggers attribute of that component. In this case we want the table – id=”customers” – to be refreshed whenever a PPR cycle is initiated by a change in a Date of Birth. We need the following code:

<af:column sortProperty="dateOfBirth" sortable="true"

www.odtug.com 9 ODTUG Kaleidoscope 2007

Page 10: jellema2_ajax.doc

Getting the most AJAX out of ADF Faces… Jellema

headerText="Date Of Birth" formatType="text"> <af:selectInputDate autoSubmit="true" value="#{customer.dateOfBirth}" valueChangeListener="#{CustomerManager.HandleDobChangeEvent}"/></af:column>

The autoSubmit on the selectInputDate ensures that whenever a Date of Birth is changed, a PPR cycle is started. The valueChangeListener allows us to capture the fact that a Date of Birth is changed in a server side method. This method will then add the table to the set of partial targets:

public void HandleDobChangeEvent(ValueChangeEvent valueChangeEvent) { Application app = FacesContext.getCurrentInstance().getApplication(); UIComponent table = ((CoreSelectInputDate)valueChangeEvent.getComponent()).findComponent("customers"); AdfFacesContext.getCurrentInstance().addPartialTarget(table); }

This code tells AdfFaces to refresh the customers table at the end of the PPR cycle, as if it has the id of the date of birth cell that was changed and thereby triggered the PPR cycle in its partialTriggers attribute. The result is that whenever a value is changed in the Date of Birth column, the average age is refreshed, as is the age itself.

Coming soon to a theater near you: the 11g Much Richer Client ComponentsWe have been looking at the current (10.1.3.x) release of ADF Faces. In the near future – the Technical Preview of Release 11g was unveiled during JavaOne, May 2007 – ADF Faces will be extended with a new set of components with very smooth, rich client side functionality. Among these are the client side drag & drop, a real accordion, a stepless data scroller and a table with resizable and repositionable columns. Many of the uses for PPR we saw in this paper will still be useful to complement all that richness, while some may become redundant. However, watch out for those 11g Rich Client Components.

ConclusionThe Partial Page Render functionality in ADF Faces allows for largely declarative AJAX behavior in our web applications. We can AJAXify ADF Faces applications with two basic steps: set autoSubmit to have components initiate a PPR cycle when changed and add the triggering components’ id value to the partialTriggers attribute to have components refreshed by the PPR action. This article shows the basic steps as well as some more advanced usages of the PPR feature. It should be clear that PPR is easy to use as well as able to bring the user experience for our JSF application to a new – mezzanine – level. Any ADF Faces or Trinidad developer should have PPR in her or his toolkit.

ResourcesAll sources for this paper can be downloaded from http://technology.amis.nl/blog?s=facesajax . The blog article The (AJA)X Files - On the built-in AJAX facilities of ADF Faces for zero-code Rich User Experience (http://technology.amis.nl/blog/?p=1211) has a lot of details and examples about AJAX in ADF Faces. Of course lots of details are found in the JDeveloper 10.1.3 On Line documentation (start from http://www.oracle.com/technology/tech/java/jsf.html) as well as blogs by among others Frank Nimphius (http://www.orablogs.com/fnimphius/) and Duncan Mills (http://www.groundside.com/blog/DuncanMills.php).

About the AuthorLucas Jellema is Expertise Manager for AMIS in Nieuwegein, The Netherlands. Before joining AMIS in 2002 he worked at Oracle Consulting's worldwide Internet Development Center of Excellence. Lucas is a frequent blogger (http://technology.amis.nl), author and presenter at international conferences (OOW, ODTUG, JavaOne) and workshops, as well as developer of reusable software such as JHeadstart Designer Generator, CDM RuleFrame, and the Oracle Designer Repository Object Browser. In 2005 Lucas was nominated Oracle ACE and in 2006 Oracle Regional Director Fusion Middleware. He is currently the only one to hold both titles. Lucas is Senior Technical Consultant on Oracle and Java projects and currently focuses on SOA, mainly through Oracle SOA Suite. He can be contacted at [email protected].

www.odtug.com 10 ODTUG Kaleidoscope 2007