Dr. Winston Prakash Ph.D. 

Personal Website

Creating an AJAX enabled JSF component - Part 1

In part 1, we will be creating  a simple Image Slider component.  The Image Slider component  takes one or more images and displays the image with a previous and a next link. Clicking on the next or previous link displays the next or previous image.   We  will add AJAX capability to our image slider component using Dynamic Faces.  Adding the AJAX capability, helps the Image Slider component  to display the next or previous image by fetching them with out submitting the form.

In part 2, we will add design time to our component and create a complib archive. This complib can be later imported in to  Netbeans Visual Web Pack and the component can be added to the the visual web application by drag and drop. The design time will allow us to specify rich user interaction when the component is added to the designer.

Following are the steps for part 1 to create the AJAX enabled custom JSF component.  You can download the completed project from here.

Initial Steps to create the skeleton project

  • Start Netbeans IDE 5.5.1
  • Create a new Java Library Project and call it ImageSlider-Runtime using File -> New Project... menu item. In the dialog select General -> Java Class Library
  •  Create a new package called com.examples.imageslider 
  • First create a Tag Library Descriptor file.
    • Click on the New File button in the toolbar or  New -> File/Folder from the menu item of the Project Folder.
    • In the Choose File Type panel of the dialog select Web -> Tag Library Descriptor.
    • In the Name and Location panel give the
      • TLD name: ImageSlider
      • Folder: META-INF/
      •  Prefix: is
      • The URI: http://com.examples.imageslider
  • Next create the Tag Handler Class
    • Click on the New File button in the toolbar  and  in the dialog choose the file type Web -> Tag Handler.
    • In the Name and Location panel provide the following
      • Class Name: ImageSliderTag
      • Package: com.examples.imageslider
    • In the TLD Information panel provide
      • Tag Name: ImageSlider
      • TLD File: src/META-INF/ImageSlider.tld
    • In the same panel add the following attributes
      • style - type String and mark this as required, if you plan to import this component in to Netbean VWP
      • width - type String
      • height - type String
      • url - type String and mark as required. The URL(s) for the image slide is specified via this attribute.
      • id - type String, the id of the component
      • binding - type String, component binding attribute.
  • Next step is to create the Component class
    • Click on the New File button again in the toolbar  and  in the dialog choose the file type Java Classes -> Java Class.
    • In the Name and Location panel provide the following
      • Class Name: ImageSlider
      • Package: com.examples.imageslider
    • Add the following properties to the ImageSlider.java using the menu item "Add -> Property" in the Bean Properties Node of the class in the project.
      • style - type String 
      • width - type String
      • height - type String
      • url - type String
      • id - type String
      • imageUrls - type String[]
      • imageIndex - type int
  • Next step is to create the Renderer Class.
    • Click on the New File button again in the toolbar  and  in the dialog choose the file type Java Classes -> Java Class.
    • In the Name and Location panel provide the following
      • Class Name - ImageSliderRenderer
      • Package - com.examples.imageslider

So far we have not written any code. We have asked the IDE to generate the necessary skeleton code for us. Now, let us modify these code to create the real component.

Step 1:  Modify the Component Class (ImageSlider.java)

First, we need JSF API classes to compile our Java Library project. So, let us add the the necessary JSF jars  to our project. The JSF 1.2 API are part of Java EE 5 . If you have Glassfish installed on your computer, then you can get the necessary jar  from its lib directories.

  •  Right click on the project node and select properties.
  • In the properties dialog right click on the Libraries Node and select JAR/Folder
  • In the dialog navigate to Glassfish lib directory and select javaee.jar.

We have created a bean class calles ImageSlider.java with set of properties. Let us modify this bean class to make it a JSF component class. To do this,  we need to extend this class with one of pre-defined JSF component type (Ex. UIInput, UIOutput). Let us extend this class with UIOutput.

Double click and ImageSlider.java from the project and open it in the editor, if it is not already opened. Edit the class and extend it with UIOutput.

public class ImageSlider extends UIOutput{

IDE shows red squiggly error line after extending the class with UIOutput. Click on the light bulb (click on the squiggly line to get the light bulb) and let IDE generate 

import javax.faces.component.UIOutput;

We can tell the JSF runtime about the family to which this component belongs to and also about the renderer type of the component by adding the following to the ImageSlider.java

private static final String IMAGE_RENDERER_TYPE = "com.examples.imageslider";
private static final String IMAGE_COMP_FAMILY = "javax.faces.Output";

public String getFamily() {
  return IMAGE_COMP_FAMILY;
}

public String getRendererType() {
  return IMAGE_RENDERER_TYPE;
}

When we navigate from one image to another in the browser, we need to save the state of the component between navigation. This is done by adding code to save the state in the component class as follows.

public Object saveState(FacesContext context) {
  Object values[] = new Object[7];
  values[0] = super.saveState(context);
  values[1] = imageUrls;
  values[2] = new Integer(imageIndex);
  values[3] = width;
  values[4] = height;
  values[5] = style;
  values[6] = id;
  return values;
}

public void restoreState(FacesContext context, Object state) {
  Object values[] = (Object[]) state;
  super.restoreState(context, values[0]);
  imageUrls = (String[]) values[1];
  imageIndex = ((Integer) values[2]).intValue();
  width = (String) values[3];
  height = (String) values[4];
  style = (String) values[5];
  id = (String) values[6];
}  

Step 2: Modify the Tag Handler Class (ImageSliderTag.java)

Double click and ImageSliderTag.java from the project and open it in the editor. You would notice, IDE has generated the class extending SimpleTagSupport.

 public class ImageSlideTag extends SimpleTagSupport{

But we want the class to extend UIComponentELTag. So edit the code and make the class to extend UIComponentELTag  instead of SimpleTagSupport.

 public class ImageSlideTag extends UIComponentELTag{ 

Note: IDE also generated the method doTag(), which is needed by SimpleTagSupport. This is no longer needed as this is already implemented in the base UIComponentELTag  class.  So remove this method and its body completely. 

Remove the red squiggly error line by click on the light bulb and ask IDE to generate 

import javax.faces.webapp.UIComponentELTag;

Click again on the light bulb and  ask the IDE to generate all the abstract methods. This action adds the methods getComponentType() & getRendererType(). Modify these two methods as follows.

private static final String IMAGE_COMP_TYPE = "com.examples.imageslider";
private static final String IMAGE_RENDERER_TYPE = "com.examples.imageslider";

public String getComponentType() {
  return IMAGE_COMP_TYPE;
}

public String getRendererType() {
  return IMAGE_RENDERER_TYPE;
}

The above is necessary to tell the JSFRuntime about the component type and component renderer type. Later we will add these information to the faces-config file. 

Next we need to override the setProperties() method of  UIComponentELTag in our Tag Handler class. Add the following to ImageSliderTag.java. In this method we set the attributes values of the Tag Handler to the component class which will be later used by the renderer.

protected void setProperties(UIComponent component) {
  super.setProperties(component);
  ImageSlider imgSlider = ((ImageSlider)component);
  if (url != null) {
    String[] imgUrls = url.trim().split(",");
    imgSlider.setUrl(imgUrls[0]);
    imgSlider.setImageUrls(imgUrls);
  }

  if (width != null) {
    imgSlider.setWidth(width);
  }

  if (height != null) {
    imgSlider.setHeight(height);
  }

  if (style != null) {
    imgSlider.setStyle(style);
  }

  if (id != null) {
    imgSlider.setStyle(style);
  }
}		

When the JSP engine encounters the tag, it sets the values specified in the attributes to the corresponding setter method in the Tag Handler class and then it invokes the doTag() method. However, doTag() method of UIComponentELTag calls the setProperties() method thus hiding many of ugly details of tag handling, but giving the sub class (ImageSlideTag)an opportunity to set the attribute values to the component as properties. These component properties will be used by the component renderer while rendering thr component.

Step 3: Modify the Component renderer (ImageSlideRenderer.java)

In this step we will modify the renderer class we have created previously.

Double click and ImageSliderRenderer.java from the project and open it in the editor, if it is not already opened. Edit the class and extend it with Renderer.

public class ImageSlideRenderer extends Renderer{

 Click on the light bulb and ask IDE to generate 

import javax.faces.render.Renderer;

Next add  the needed helper functions: getForm(), getHiddenFieldName(), getPrevLinkName(),  getNextLinkName(), getPostbackFunctionName(), and renderJavaScript()

private UIForm getForm(UIComponent component) {
   UIComponent parent = component.getParent();
   while (parent != null) {
      if (parent instanceof UIForm) {
         break;
      }
      parent = parent.getParent();
   }
   if (parent == null) {
       throw new IllegalStateException("Not nested inside a form!");
   }
   return (UIForm) parent;
}

			
private String getPostbackFunctionName(UIComponent component){
  ImageSlider imgSlider = (ImageSlider)component; 
  return imgSlider.getId() + "PostBack";
}

Next we are going to write the code that would writes out the JavaScript needed for Ajax Transaction. The essence is in the post back function that is called when the Next or Previous link is clicked (onClick()) which in turn calls the DynaFaces.fireAjaxTransaction.

private void renderJavaScript(FacesContext context, UIComponent component) 
                                                           throws IOException{
        ImageSlider imgSlider = (ImageSlider)component;
        ServletContext servletContext = (ServletContext) 
                           context.getExternalContext().getContext();
        String contextPath = servletContext.getContextPath();
        String id = (String)imgSlider.getClientId(context);
        
        ResponseWriter writer = context.getResponseWriter();
         
        UIForm form = getForm(imgSlider);
        String formClientId = form.getClientId(context);
        
        writer.startElement("script", imgSlider);
        writer.writeAttribute("type", "text/javascript", null);
        writer.writeAttribute("src", contextPath + 
          "/faces/static/META-INF/libs/scriptaculous/version1.6.4/prototype.js", null);
        writer.endElement("script");
        
        writer.startElement("script", imgSlider);
        writer.writeAttribute("type", "text/javascript", null);
        writer.writeAttribute("src", contextPath + 
          "/faces/static/META-INF/com_sun_faces_ajax.js", null);
        writer.endElement("script");
        
        writer.startElement("script", imgSlider);
        writer.writeAttribute("type", "text/javascript", null);
        String varFormName = form.getId() + imgSlider.getId() + "Form";
        String script = "\nvar " + varFormName + " = document.forms['" 
                + formClientId + "'];" + "\nfunction" + " " 
                + getPostbackFunctionName(imgSlider) + "(element) {\n"
                + "      document.getElementById('" 
                + getHiddenFieldName(context, imgSlider) + "').value = element.id; \n"
                + "      DynaFaces.fireAjaxTransaction(element,{execute:'" + id
                + "',render:'" + id + "',inputs:'" 
                + getHiddenFieldName(context, imgSlider) + "'});" +
                "  \n} \n";
        writer.writeComment(script);
        writer.endElement("script");
}

Note: In the above method we are using JavaScripts from libraries at META-INF/libs. These are part of Dynamic Faces which we will be adding to the Web Application. 

In renderer class, we write the actual HTML that would be send to the browser as part of rendering this component. The base class Renderer gives us couple of overridable methods such as encodeBegin(), encodeChildren() and encodeEnd().  Since our component has no children, let us place all the rendering code in the overridden encodeBegin() method as follows.

Add method encodeBegin(). This method is responsible for rendering the HTML as shown below (pseudo HTML)

<table>
   <tr>
      <td>
         <a onclick="javascript:PostBack(this)"> <!--prev link-->
      </td>
      <td>
         <img url=".."> <!--image box-->
      </td>
      <td>
         <a onclick="javascript:PostBack(this)"> <!--next link-->
      </td>
   <tr>
</table>

public void encodeBegin(FacesContext context, UIComponent component) 
                                                     throws IOException {
  ImageSlider imgSlider = (ImageSlider)component;
  ServletContext servletContext = (ServletContext) 
                    context.getExternalContext().getContext();
  String contextPath = servletContext.getContextPath();
        
  ResponseWriter writer = context.getResponseWriter();
  writer.startElement("table", imgSlider);
  String id = (String)imgSlider.getClientId(context);
  writer.writeAttribute("id", id, null);
        
  // get image elements height and width
  String width = imgSlider.getWidth();
  String height = imgSlider.getHeight();
        
  // Add code to implement "style" attribute for image
  String style = imgSlider.getStyle();
  style = (style!=null) ? style + ";" : "";
        
  if (width != null){
    style += "width:" + width + ";";
  }
        
  if (height != null){
    style += "height:" + height + ";";
  }
        
  writer.writeAttribute("style", style, null);
        
  // Render the Script element that does the AJAX transaction
  renderJavaScript(context, component);
        
  // Hidden field to hold prev/next image information
        
  writer.startElement("input", imgSlider);
  writer.writeAttribute("type", "hidden", null);
  writer.writeAttribute("id", getHiddenFieldName(context, imgSlider), null);
  writer.writeAttribute("value", "", null);
  writer.endElement("input");
        
  // Render the tr Element
  writer.startElement("tr", imgSlider);
        
  // Render the td Element for prev link
  writer.startElement("td", imgSlider);
        
  // Render Next Image link
  writer.startElement("a", imgSlider);
  writer.writeAttribute("onClick", "javascript:" 
      + getPostbackFunctionName(imgSlider) + "(this)", null);
  writer.writeAttribute("id", getPrevLinkName(context, component), null);
  String prevStr = "<";
  writer.writeText(prevStr.toCharArray(),0,prevStr.length());
  writer.endElement("a");
        
  writer.endElement("td");
        
  // Render the td Element for image
  writer.startElement("td", imgSlider);
        
  // Render the Image
        
  writer.startElement("img", imgSlider);
  writer.writeAttribute("src", contextPath + imgSlider.getUrl(), "url");
  writer.writeAttribute("width", imgSlider.getWidth(), "width");
  writer.writeAttribute("height", imgSlider.getHeight(), "height");
        
  writer.endElement("td");
        
  // Render the td Element for next link
  writer.startElement("td", imgSlider);
        
  // Render Next Image link
  writer.startElement("a", imgSlider);
  writer.writeAttribute("onClick", "javascript:" 
       + getPostbackFunctionName(imgSlider) + "(this)", null);
  writer.writeAttribute("id", getNextLinkName(context, component), null);
  String nextStr = ">";
  writer.writeText(nextStr.toCharArray(),0,nextStr.length());
  writer.endElement("a");
        
  writer.endElement("td");
        
  writer.endElement("tr");
        
  writer.endElement("table");
}

Now, add the decode() method to ImageSliderRenderer.java. Decode method is responsible for decoding the inputs received from the broswer, when the page is submited. It adjusts the imgUrls index value depending on whether the hidden field value corresponds to the previous or next link. It also sets the ImageSlider's url property using the new index value.

public void decode(FacesContext context, UIComponent component) {
  if (context == null || component == null) {
      throw new NullPointerException();
  }
  ImageSlider imgSlider = (ImageSlider)component;
        
  String clientId = imgSlider.getClientId(context);
  String hiddenFieldName = getHiddenFieldName(context, imgSlider);
&  Map requestParameterMap = context.getExternalContext().
                                       getRequestParameterMap();
  String value = (String) equestParameterMap.get(hiddenFieldName);
        
  String[] imgUrls = imgSlider.getImageUrls();
        
  int imgIndex = imgSlider.getImageIndex();
        
  if (value.equals(getPrevLinkName(context, component))){
    if (imgIndex > 0){
       imgSlider.setImageIndex(--imgIndex);
    }
  }else if (value.equals(getNextLinkName(context, component))){
    if (imgIndex < imgUrls.length - 1){
      imgSlider.setImageIndex(++imgIndex);
    }
  }
  imgSlider.setUrl(imgUrls[imgIndex]);
}

Right-click inside the editor and select Fix Imports.

Step 4: Modify the faces-config file

We are almost ready with the component. Now we need to tell the JSF run time about our component. This is done through one of the the faces-config file.  In the Projects window, expand the node Source Packages/META-INF. Right-click META-INF and select New > File/Folder. In the  dialog select  XML Categories and XML Document as File Typ. Create the file by name faces-config.xml.

Let us add the ImageSlider component information to the faces-config file. Double click and open the faces-config.xml if it is not opened and add the following to it.  

<?xml version='1.0' encoding='UTF-8'?>
  <faces-config version="1.2" 
         xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
         http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
   <component>
    <component-type>
      com.examples.imageslider
    </component-type>
    <component-class>
      com.examples.imageslider.ImageSlider
    </component-class>
   </component>

   <render-kit>
    <renderer>
     <description>
       Renderer for the Image Slider component.
     </description>
     <component-family>
       javax.faces.Output
     </component-family>
     <renderer-type>
       com.examples.imageslider
     </renderer-type>
     <renderer-class>
       com.examples.imageslider.ImageSliderRenderer
     </renderer-class>
    </renderer>
   </render-kit>  
</faces-config>

If you notice, we have specified the component-type and renderer-type exactly as we specified in the Tag Handler class (ImageSliderTag.java). /p>

Step 5: Testing the custom built component

Congratulations!. We have built an Ajax enabled custom JSF component from scratch. Now it is time to test our newly built component. 

First, let us create a new web project and add few images to the project using the following steps.

  • Create a web project called ImageSlider-Test. In the Framework panel select Java Server Faces.
  • Create a folder called "resources"  under Web Pages in the project. Right click Web Pages and select New -> Folder to create the folder
  • Copy few images in to this newly created folder. Select the "resources" folder. Then from the File Menu select the menu item Add Existing Item -> Image File

Note, when we create the Web project with JSF frame work above, IDE has created a web page called welcomeJSF.jsp. We can add our component to this page  and test it to display the images we added to the resources folder.

  • First configure the Deployment Descriptor. Dynamic Faces technology requires an initialization parameter in the web application's deployment descriptor.
    • Dubleo Click and open the web.xml (expand Web Pages and expand WEB-INF)
    • In the editing toolbar, click Servlets, then click the Add button that appears under Initialization Parameters  and type
      • Param Name: javax.faces.LIFECYCLE_ID
      • Param Value: com.sun.faces.lifecycle.PARTIAL
  •  Add the required Dynamic Faces libraries to the project
    • Download the Dynamic faces libraries.
    • Open the ImageSlider-Test project, right click on the Libraries node and select Add Jar/Folder and add the following jars
      • commons-logging-1.1.jar
      • jsf-extensions-common-0.1.jar
      • jsf-extensions-dynamic-faces-0.1.jar
      • shale-remoting-1.1.0-swdp-a.jar
  • Add the ImageSlider-runtime project as dependent project
    • Open the ImageSlider-Test project, right click on the Libraries node and select Add Project and in the dialog set the location of project ImageSlider-runtime
  • Next step is to add our tag library definition to this welcomeJSF.jsp. Double click and open the  JSP and add the tag lib as follows
<%@taglib prefix="is" uri="http://com.examples.imageslider"%>
			
  • Finally add the component inside the <h:view> tag as follows
<f:view>
  <h:form>
   <is:ImageSlider id="imageSlider1" 
     url="/resources/image1.jpg,/resources/image2.jpg" 
     style="border: medium solid #000080;"
     width="200" height="150"/>
   </h:form>
</f:view> 
			
  • Execute the web application by clicking on the green arrow button in the tool bar. 

Bingo!. Our ImageSlider component displays the images we specified in the JSP page, once you click the JavaServer Faces Welcome Page  in the first page. It should look something like the following. Click on the Next and Previous links to see the previous and next image. Note when you click on the Next and Previous link, the page is not submitted. But the Image is fetched using Ajax Transaction.