JSF 2 fu: Ajax components

Implement reusable, Ajaxified components with JSF 2

Java™Server Faces (JSF) 2 Expert Group member David Geary begins a new article series offering in-depth coverage of JSF 2 technology. In this installment, you'll learn how to integrate JSF 2's composite components with the framework's support for Ajax development.

David Geary, President, Clarity Training, Inc.

Photo of David GearyAuthor, speaker, and consultant David Geary is the president of Clarity Training, Inc., where he teaches developers to implement Web applications using JSF and Google Web Toolkit (GWT). He was on the JSTL 1.0 and JSF 1.0/2.0 Expert Groups, co-authored Sun's Web Developer Certification Exam, and has contributed to open source projects, including Apache Struts and Apache Shale. David's Graphic Java Swing was one of the best-selling Java books of all time, and Core JSF (co-written with Cay Horstman), is the best-selling JSF book. David speaks regularly at conferences and user groups. He has been a regular on the NFJS tour since 2003, has taught courses at Java University, and was twice voted a JavaOne rock star.



27 April 2010

Also available in Chinese Russian Portuguese

Among JSF 2's many new features, two of the most compelling are arguably composite components and Ajax support. But their strength is most apparent when the two are combined, making it easy to implement Ajax-enabled custom components with a minimum of fuss.

About this series

The JSF 2 fu series, a follow-on to David Geary's three-article introduction of the same name, will help you develop and hone your JSF 2 framework skills like a kung fu master. The current series dives deeper into the framework and its surrounding ecosystem. And it takes a peek outside the box by showing how some Java EE technologies, such as Contexts and Dependency Injection, integrate with JSF.

In this article, I'll show you how to implement an autocomplete component that uses Ajax to manage its list of completion items. In doing so, you'll see how you can integrate Ajax into your own composite components.

The code for this series is based on JSF 2 running in an enterprise container, such as GlassFish or Resin. The last section in this article is a step-by-step tutorial on installing and running the article's code with GlassFish.

A JSF autocomplete custom component

Made famous by Google's search field, autocomplete fields (also known as suggest boxes), are a staple of many Web applications. They are also a typical use case for Ajax. Autocomplete fields come with most Ajax frameworks, such as Scriptaculous and JQuery, as Figure 1— a look at AjaxDaddy's collection of autocomplete components (see Resources) — attests:

Figure 1. AjaxDaddy autocomplete components
Screen shot of AjaxDaddy

This article will explore one way to implement an Ajax-enabled autocomplete field with JSF. You'll see how to implement the autocomplete field shown in Figure 2, which shows a short list of fictional countries (culled from Wikipedia's "List of fictional countries" article; see Resources):

Figure 2. The autocomplete field
The autocomplete field

Figure 3 and Figure 4 show the autocomplete field in action. In Figure 3, when Al is typed into the field, the country list is reduced to names that start with those two letters:

Figure 3. Completion items that start with Al
Completion items that start with Al

Similarly, Figure 4 shows the results when Bar is typed into the field. The list shows only country names that begin with Bar:

Figure 4. Completion items that start with Bar
Completion items that start with Bar

Using the autocomplete component

Composite components: The basics

If you aren't familiar with using or implementing JSF 2 composite components, you'll find an introduction in "JSF 2 fu, Part 2: Templating and composite components."

The Locations autocomplete field is a JSF composite component, and it is used in a facelet, as shown in Listing 1:

Listing 1. The facelet
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:util="http://java.sun.com/jsf/composite/util">
   <h:head>
      <title>#{msgs.autoCompleteWindowTitle}</title>
   </h:head>

   <h:body>
      <div style="padding: 20px;">
         <h:form>
            <h:panelGrid columns="2">
               #{msgs.locationsPrompt}
               <util:autoComplete value="#{user.country}"
                     completionItems="#{autoComplete.countries}" />
            </h:panelGrid>
         </h:form>
      </div>
   </h:body>
</html>

The facelet in Listing 1 uses the autoComplete composite component by declaring an appropriate namespace —util— and using the component's associated tag, <util:autoComplete>.

Notice the two attributes for the <util:autoComplete> tag in Listing 1:

  • value is the country property of a managed bean named user.
  • completionItems is the initial set of completion items for the field.

The User class is a simple managed bean, obviously contrived for just this occasion. Its code is shown in Listing 2:

Listing 2. The User class
package com.corejsf;

import java.io.Serializable;

import javax.inject.Named; 
import javax.enterprise.context.SessionScoped; 

@Named()
@SessionScoped
public class User implements Serializable {
  private String country;
  public String getCountry() { return country; }
  public void setCountry(String country) { this.country = country; }
}

Notice the @Named annotation, which, along with @SessionScoped, instantiates a managed bean nameduser and places it in session scope the first time JSF encounters #{user.country} in a facelet. This application's only reference to #{user.country} takes place in Listing 1, where I specify the country property of the user managed bean as the value for the <util:autoComplete> component.

Listing 3 shows the AutoComplete class, which defines the countries property that I specified as the autocomplete component's list of completion items:

Listing 3. The completion items
package com.corejsf;

import java.io.Serializable;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named
@ApplicationScoped
public class AutoComplete implements Serializable {
   public String[] getLocations() {
      return new String[] {
    		  "Abari", "Absurdsvanj", "Adjikistan", "Afromacoland",
    		  "Agrabah", "Agaria", "Aijina", "Ajir", "Al-Alemand",
    		  "Al Amarja", "Alaine", "Albenistan", "Aldestan",
    		  "Al Hari", "Alpine Emirates", "Altruria",
    		  "Allied States of America", "BabaKiueria", "Babalstan",
    		  "Babar's Kingdom","Backhairistan", "Bacteria",
    		  "Bahar", "Bahavia", "Bahkan", "Bakaslavia",
    		  "Balamkadar", "Baki", "Balinderry", "Balochistan",
    		  "Baltish", "Baltonia", "Bataniland, Republic of",
    		  "Bayview", "Banania, Republica de", "Bandrika",
    		  "Bangalia", "Bangstoff", "Bapetikosweti", "Baracq",
    		  "Baraza", "Barataria", "Barclay Islands",
    		  "Barringtonia", "Bay View", "Basenji",
      };
   }
}

That's all there is to using the autocomplete component. Now you'll see how it works.


How the autocomplete component works

The autocomplete component is a JSF 2 composite component, so, like most composite components, it is implemented in an XHTML file. The component consists of a text input and a listbox, and some JavaScript. Initially, the listbox's style is display: none, which makes the listbox invisible.

The autocomplete component responds to three events:

  • keyup events in the text input
  • blur (losing focus) events in the text input
  • change (selection) events in the listbox

When the user types in the text input, the autocomplete component calls a JavaScript function for every keyup event. That function coalesces keystroke events to make no more than one Ajax call every 350ms. So, in response to keyup events in the text input, the autocomplete component makes an Ajax call, at most every 350ms, to the server. (All of that is to prevent fast typists from flooding the server with Ajax calls. In practice, coalescing events may be overrated in this case, but it affords an opportunity to illustrate coalescing events in JavaScript, which in general is a useful tool.)

When the user selects an item from the listbox, the autocomplete component makes another Ajax call to the server.

Both the text input and the listbox have listeners attached to them that do most of the meaningful work on the server during Ajax calls. In response to keyup events, the text input's listener updates the listbox's completion items. In response to listbox selection events, the listbox's listener copies the listbox's selected item into the text input and hides the listbox.

Now that you have a good idea of how the autocomplete component works, you're ready to take a look at its implementation.


Implementing the autocomplete component

The autocomplete component implementation consists of these artifacts:

  • A composite component
  • A handful of JavaScript functions
  • A value-change listener that updates completion items

I'll start with the composite component in Listing 4:

Listing 4. The autoComplete component
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"    
    xmlns:composite="http://java.sun.com/jsf/composite">
    
    <!-- INTERFACE -->
    <composite:interface>
      <composite:attribute name="value" required="true"/>
      <composite:attribute name="completionItems" required="true"/>
    </composite:interface> 

    <!-- IMPLEMENATION -->          
    <composite:implementation>
      <div id="#{cc.clientId}">
        <h:outputScript library="javascript" 
           name="prototype-1.6.0.2.js" target="head"/>
        
        <h:outputScript library="javascript" 
           name="autoComplete.js" target="head"/>
      
        <h:inputText id="input" value="#{cc.attrs.value}" 
           onkeyup="com.corejsf.updateCompletionItems(this, event)"
           onblur="com.corejsf.inputLostFocus(this)"
           valueChangeListener="#{autocompleteListener.valueChanged}"/>
            
        <h:selectOneListbox id="listbox" style="display: none"
           valueChangeListener="#{autocompleteListener.completionItemSelected}">
        
            <f:selectItems value="#{cc.attrs.completionItems}"/>
            <f:ajax render="input"/>
          
        </h:selectOneListbox>
      <div>
    </composite:implementation>    
</ui:composition>

Three things are going on in Listing 4's implementation section. First, the component makes Ajax calls in response to keyup events in the text input, and it hides the listbox when the text input loses focus by virtue of JavaScript functions assigned to keyup and blur events in the text input.

Second, the component makes Ajax calls in response to change events in the listbox with JSF 2's <f:ajax> tag. When the user makes a selection from the listbox, JSF makes an Ajax call to the server and updates the text input's value when the Ajax call returns.

Wrap composite components in a <div>

The composite component in Listing 4 wraps its implementation in a <div> with the client identifier of the composite component. That lets other components reference the autocomplete component by its client ID. For example, another component might want to execute or render one or more autocomplete components during an Ajax call.

Third, both the text input and the listbox have value-change listener methods attached to them, so when JSF makes Ajax calls in response to the user typing in the text input, JSF invokes the text input's value-change listener on the server. When the user selects an item from the listbox, JSF makes an Ajax call to the server and invokes the listbox's value-change listener.

Listing 5 shows the JavaScript used by the autocomplete component:

Listing 5. The JavaScript
if (!com)
   var com = {}

if (!com.corejsf) {
   var focusLostTimeout
   com.corejsf = {
      errorHandler : function(data) {
         alert("Error occurred during Ajax call: " + data.description)
      },

      updateCompletionItems : function(input, event) {
         var keystrokeTimeout

         jsf.ajax.addOnError(com.corejsf.errorHandler)

         var ajaxRequest = function() {

            jsf.ajax.request(input, event, {
               render: com.corejsf.getListboxId(input),
               x: Element.cumulativeOffset(input)[0],
               y: Element.cumulativeOffset(input)[1]
                     + Element.getHeight(input)
            })
         }

         window.clearTimeout(keystrokeTimeout)
         keystrokeTimeout = window.setTimeout(ajaxRequest, 350)
      },

      inputLostFocus : function(input) {
         var hideListbox = function() {
            Element.hide(com.corejsf.getListboxId(input))
         }

         focusLostTimeout = window.setTimeout(hideListbox, 200)
      },

      getListboxId : function(input) {
         var clientId = new String(input.name)
         var lastIndex = clientId.lastIndexOf(':')
         return clientId.substring(0, lastIndex) + ':listbox'
      }
   }
}

The JavaScript in Listing 5 consists of three functions that I placed inside a namespace named com.corejsf. I implemented the namespace (which is technically a JavaScript literal object) to prevent someone from accidentally (or not) clobbering any of my three functions.

If those functions were not tucked away inside com.corejsf, someone could implement their own updateCompletionItems function, thereby replacing my implementation with theirs. It's feasible that some JavaScript library might implement a function named updateCompletionItems, but it's a pretty good bet nobody's going to come up with com.corejsf.updateCompletionItems. (In retrospect, dropping the com, and going with corejsf.updateCompletionItems probably would've sufficed, but sometimes it's easy to get carried away.)

So, what do the functions do? The updateCompletionItems() function makes an Ajax request to the server — by calling JSF's jsf.ajax.request() function — asking only that JSF render the listbox component when the Ajax call returns. The updateCompletionItems() function also passes two extra parameters to jsf.ajax.request(): the x and y coordinates of the upper left-hand corner of the listbox. The jsf.ajax.request() function turns those function parameters into request parameters that it sends with the Ajax call.

JSF calls the inputLostFocus() function when the text input loses focus. That function simply hides the listbox, using Prototype's Element object.

Both updateCompletionItems() and inputLostFocus() store their functionality in a function. Then they schedule their functions to execute in 350ms and 200ms, respectively. In other words, each function has a job to do, but it delays that job for either 350ms or 200ms. The text input delays after a keyup event, so that the updateCompletionItems() method sends an Ajax request once per 350ms, at most. The idea is that if the user is an (extremely!) fast typist, you don't want to flood the server with Ajax calls.

The inputLostFocus function, called when the text input loses focus, delays its work for 200ms. That delay is needed because the value will be copied out of the listbox when the Ajax call returns, and the listbox must be visible for that to work.

Finally, notice the getListBoxId() function. That helper function obtains the client identifier of the listbox from the client identifier of the text input. The function is able to do that because it's in cahoots with the autoComplete component in Listing 4. The autoComplete component assigns input and listbox as the component identifiers for the text input and listbox respectively, so the getListBoxId() function merely chops off input and appends listbox to get from the text input's client identifier to the listbox's.

Listing 6 shows the implementation of the listener that pulls everything together:

Listing 6. The listener
package com.corejsf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.SessionScoped;
import javax.faces.component.UIInput;
import javax.faces.component.UISelectItems;
import javax.faces.component.UISelectOne;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.inject.Named;

@Named
@SessionScoped
public class AutocompleteListener implements Serializable {
   private static String COMPLETION_ITEMS_ATTR = "corejsf.completionItems";
  
   public void valueChanged(ValueChangeEvent e) {
      UIInput input = (UIInput)e.getSource();
      UISelectOne listbox = (UISelectOne)input.findComponent("listbox");

      if (listbox != null) {
         UISelectItems items = (UISelectItems)listbox.getChildren().get(0);
         Map<String, Object> attrs = listbox.getAttributes();
         List<String> newItems = getNewItems((String)input.getValue(),
            getCompletionItems(listbox, items, attrs));

         items.setValue(newItems.toArray());
         setListboxStyle(newItems.size(), attrs);
      }
   }
  
   public void completionItemSelected(ValueChangeEvent e) {
     UISelectOne listbox = (UISelectOne)e.getSource();
     UIInput input = (UIInput)listbox.findComponent("input");
    
     if(input != null) {
        input.setValue(listbox.getValue());
     }
     Map<String, Object> attrs = listbox.getAttributes();
     attrs.put("style", "display: none");
   }
   
   private List<String> getNewItems(String inputValue, String[] completionItems) {
      List<String> newItems = new ArrayList<String>();
    
      for (String item : completionItems) {
         String s = item.substring(0, inputValue.length());
         if (s.equalsIgnoreCase(inputValue))
           newItems.add(item);
      }
    
      return newItems;
   }
  
   private void setListboxStyle(int rows, Map<String, Object> attrs) {
      if (rows > 0) {
         Map<String, String> reqParams = FacesContext.getCurrentInstance()
            .getExternalContext().getRequestParameterMap();
      
         attrs.put("style", "display: inline; position: absolute; left: "
             + reqParams.get("x") + "px;" + " top: " + reqParams.get("y") + "px");

         attrs.put("size", rows == 1 ? 2 : rows);
      }
      else
         attrs.put("style", "display: none;");
   }

   private String[] getCompletionItems(UISelectOne listbox,
      UISelectItems items, Map<String, Object> attrs) {
         Strings] completionItems = (String[])attrs.get(COMPLETION_ITEMS_ATTR);
    
         if (completionItems == null) {
            completionItems = (String[])items.getValue();
            attrs.put(COMPLETION_ITEMS_ATTR, completionItems);
         }
      return completionItems;
   }
}

JSF invokes the listener's valueChanged() method during Ajax calls in response to keyup events in the text input. That method creates a new set of completion items and then sets the listbox's items to this new set. The method also sets style attributes for the listbox that determine whether the listbox is displayed when the Ajax call returns.

The setListboxStyle() method in Listing 6 uses the x and y request parameter values that I specified when I made an Ajax call in Listing 5.

JSF invokes the listener's only other public method, completionItemSelected(), during Ajax calls in response to selection events in the listbox. That method copies the listbox's value into the text input and hides the listbox.

Notice that the valueChanged() method also stores the original completion items in an attribute of the listbox. Because each autoComplete component maintains its own list of completion items, multiple autoComplete components can peacefully coexist in the same page without stomping on one another's completion items.


Running the examples with GlassFish and Eclipse

The code in this series of articles is best suited to a JEE 6 container, such as GlassFish or Resin. You can get things to work with a servlet container, such as Tomcat, but note the "get things to work" part. Since my goal is to focus on your getting the full potential out of JSF 2 and JEE 6, and not on configuration issues, I will stick to GlassFish v3.

For the rest of this article, I'll show you how to run this article's sample code using GlassFish v3 and Eclipse. The instructions here will also suffice for the code from the rest of this series of articles. (I'm using Eclipse 3.4.1, so the closer you can match that when running the examples, the better.)

Figure 5 shows the directory structure that you'll find in the code for this article. (See Download to get the code now.) There's an autoComplete directory containing the application and an empty workspace directory for Eclipse.

Figure 5. Source code in this article's download
Screen shot of directory structure

Now that you have the code, you're almost ready to get it running. First, you need the GlassFish Eclipse plug-in, which you can download at https://glassfishplugins.dev.java.net, shown in Figure 6:

Figure 6. The GlassFish Eclipse plug-in
Screen shot of GlassFish eclipse plug-in Web page

Follow the installation instructions for the plug-in, and you're ready to go.

To install the code for this article, create a Dynamic Web project in Eclipse. You can do that from the File > New menu: if you don't see Dynamic Web project there, select Other, and in the ensuing dialog open the Web folder and select Dynamic Web Project, as shown in Figure 7:

Figure 7. Creating a Dynamic Web project
Screen shot of Dynamic Web project dialog

The next step is to configure the project. Make the following selections on the first screen of the New Dynamic Web Project wizard, as shown in Figure 8:

  1. Under Project contents, leave the Use default box unchecked. In the Directory field, enter (or browse to) the sample code's autoComplete directory.
  2. For Target Runtime, select GlassFish v3 Java EE 6.
  3. For Dynamic Web Module version, enter 2.5.
  4. For Configuration, select Default Configuration for GlassFish v3 Java EE 6.
  5. Under EAR Membership, leave the Add project to an EAR box unchecked, and enter autoCompleteEAR in the EAR Project Name: field.
Figure 8. Configuring the application, step 1
Screen shot of creating a Dynamic Web project

Click Next, then enter the values shown in Figure 9:

  1. For Context Root: enter autoComplete.
  2. For Content Directory: enter web.
  3. For Java Source Directory: enter src/java. Leave the Generate deployment descriptor box unchecked.
Figure 9. Configuring the application, step 2
Screen shot of configuring a Web module

Now you should have an autoComplete project, visible in Eclipse's Project Explorer view, as shown in Figure 10:

Figure 10. The autoComplete project
Screen shot of the autoComplete project visible in Eclipse's Project Explorer view

Now select the project, right click on it, and select Run on Server, as shown in Figure 11:

Figure 11. Run on server in Eclipse
Screen shot showing the menu item, Run on server, in Eclipse

Select GlassFish v3 Java EE 6 from the list of servers in the Run On Server dialog, shown in Figure 12:

Figure 12. Selecting GlassFish
Screen shot depicting GlassFish being selected in Run On Server

Click Finish. Eclipse should start GlassFish and, subsequently, the autoComplete application, as shown in Figure 13:

Figure 13. Running in Eclipse
Running in Eclipse

Conclusion

JSF 2 makes it easy to create powerful Ajax-enabled custom components. You don't have to implement a Java-based component or renderer, or declare that component or renderer in XML, or integrate third-party JavaScript to make Ajax calls. With JSF 2, all you need to do is create a composite component, with markup almost identical to any JSF 2 facelet view, and perhaps add a little JavaScript or Java code, and voilà — you have a cool custom component that will make data input a breeze for your application's users.

In the next installment of JSF fu, I'll discuss more aspects of implementing Ajaxified JSF custom components, such as integrating the <f:ajax> tag so your custom components can participate in Ajax initiated by others.


Download

DescriptionNameSize
Sample code for this articlej-jsf2fu-0410-src.zip39KB

Resources

Learn

Get products and technologies

  • JSF: Download JSF 2.0.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=485729
ArticleTitle=JSF 2 fu: Ajax components
publish-date=04272010