Using Default Renderers in JavaServer Faces Technology to Add AJAX Functionality to Existing Components

   
By Mark Basler, August 2006  

Developers typically add Asynchronous JavaScript and XML (AJAX) functionality to existing web pages by inserting specialized JavaScript technology code. A user event triggers this JavaScript code, which then initiates one or more background requests that are asynchronous -- that is, the requests occur at any time without regard to any other events that the user may trigger -- that interact in some way with the server. The server returns a response, which is then presented to the user courtesy of more JavaScript code. Hence, for each AJAX-enabled web page, the JavaScript functions must always be present, and the developer must code all the events that trigger the requests. Before long, any AJAX programmer begins to consider reusing components rather than writing new ones for each event.

If you want to make your AJAX functionality available to a large community of users, JavaServer Faces technology, often referred to as JSF, is a great vehicle to build a library of reusable components. Creating custom components using JavaServer Faces technology helps to encapsulate the inner workings of a component, allowing the developer to hide complex code from end users. However, this requires you to be familiar with writing custom JavaServer Faces components. If you're not familiar with how to do this, read the following articles:

When creating a custom AJAX JavaServer Faces component, developers must weigh a variety of options. However, most developers using JavaServer Faces technology will face one key question: How will the component render its markup?

Traditionally, if the component you are developing has new functionality that is not closely related to other existing components, you would create a custom renderer for the component to generate the targeted markup. However, in some cases, the component to be created adds functionality to a component that already exists. This is often the case when adding AJAX functionality to an existing component. In such a case, rather than reimplementing the base rendering behavior of the entire existing component, the developer would more efficiently reuse what already exists.

Given the Java technology developer's object-oriented mentality, a common approach would be either to extend or delegate to the existing renderer, only coding what is necessary to expose the AJAX functionality. Because the component's renderers are specific to an implementation, it would not be wise to try to extend the base renderer class. In fact, the reference implementation of JavaServer Faces 1.2 has made these classes final, so they cannot be extended. Currently, the only option that is not vendor specific is to delegate to the base renderer using standard JavaServer Faces technology method calls.

The Java BluePrints Solutions Catalog's AJAX FileUpload component shows how to use the default JavaServer Faces renderers when creating a custom component. The FileUpload component was designed to add AJAX functionality to the JavaServer Faces technology's javax.faces.component.UIForm component. Because the markup to be rendered for the FileUpload component would be the same as the UIForm with some default attributes set, coding the renderer from scratch would not make sense. With the javax.faces.component.UIForm component as a guide, you can use either of two methods to render the component's markup using base renderer functionality:

Method 1: The Custom Renderer Calls a Default Renderer for Base Functionality

Using a custom renderer to call a default component renderer can allow you to easily add functionality that does not alter the way the base component is rendered. For example, the AJAX FileUpload component's custom tag uses this method by performing the following steps, which this article will show later in greater detail:

  1. The AJAX FileUpload component both adds and removes attributes that were available in the default JavaServer Faces technology form tag. The removed attributes were set to required values in the FileUpload's custom renderer, and the added attributes were set as hidden fields to be sent when the user submitted the form.

  2. Once the appropriate values are populated in the HtmlForm component, the default renderer is retrieved through the FacesContext object.

  3. The default renderer's encodeBegin() and encodeEnd() methods are called, passing in the HtmlForm component that has been updated to hold the FileUpload's specific values.

  4. The default renderer renders the component just as it would have if the custom tag or renderer were not used.

Following are some code snippets that demonstrate this approach.

FileUpload Custom Tag Artifacts

The tag handler for the FileUpload tag links the base JavaServer Faces technology component javax.faces.HtmlForm with the custom renderer FileUploadForm. The custom renderer is defined in the FileUpload faces-config file, as is the component family.

                    public class FileUploadTag extends UIComponentELTag {
    ...
    public String getComponentType() {
        return ("javax.faces.HtmlForm");
    }
    
    public String getRendererType() {
        return ("FileUploadForm");
    }
   ...
}
                
 

The faces-config.xml File

<render-kit>
  <renderer>
    <description>
      Renderer for ajax fileupload component
    </description>
    <component-family>  
                        javax.faces.Form
    </component-family>
    <renderer-type>  
                        FileUploadForm
    </renderer-type>
    <renderer-class>
      com.sun.javaee.blueprints.components.ui.fileupload.FileUploadRenderer
    </renderer-class>
  </renderer>
</render-kit>
                
 

The FileUploadRenderer Class

The FileUploadRenderer.encodeBegin() method sets HtmlForm default attributes and looks up the default renderer for the javax.faces.Form component family and renderer type. The FileUploadRenderer.encodeBegin() method then delegates the rendering to the base renderer's encodeBegin() method, using the modified component. The FileUpload component's children are set to render themselves, so no action is necessary in the FileUploadRenderer. The FileUploadRenderer.encodeEnd() method writes hidden fields into the form's body and then delegates the rendering to the base renderer's encodeEnd() method, using the modified component.

public void encodeBegin(FacesContext context, 
  UIComponent component) throws IOException {
    if ((context == null) || (component == null)) {
        throw new NullPointerException();
    }
    HtmlForm outComp = (HtmlForm)component;
    // Custom setup of component
    outComp.setEnctype("multipart/form-data");
    ....
  
                      // Let the default renderer for the
                  

  
                      // form render all the basic form attributes.
                  

  
                      Renderer baseRenderer=
                  

  
                        context.getRenderKit().getRenderer("javax.faces.Form",
                  

  
                        "javax.faces.Form");
                  

  
                      baseRenderer.encodeBegin(context, outComp);
}


public void encodeEnd(FacesContext context, 
  UIComponent component) throws IOException {
    ResponseWriter writer = context.getResponseWriter();
        
    // Get properties from UIOutput that the 
    // specific taghandler had set.
    String serverLocationDir=
      (String)component.getAttributes().get("serverLocationDir");
    // Add a hidden field to represent the location 
    // of the directory on the server.
    // If it does not exist, it will be defaulted.
    if(serverLocationDir != null) {
        writer.startElement("input", component);
        writer.writeAttribute("type", "hidden", null);
        writer.writeAttribute("name", 
          id + "_" + FileUploadUtil.SERVER_LOCATION_DIR, null);
        writer.writeAttribute("value", serverLocationDir, null);
        writer.endElement("input");
        writer.write("\n");
    }
  
                      // Let the default renderer for the form render
                  

  
                      // all the basic form attributes.
                  

  
                      Renderer baseRenderer=context.getRenderKit().getRenderer(
                  

  
                        "javax.faces.Form", "javax.faces.Form");
                  

  
                      baseRenderer.encodeEnd(context, component);
}
                
 

This is a straightforward approach to customizing the functionality of existing components without having to reimplement the default renderer's functionality. Because the FileUpload is an AJAX component, which is not submitted through the conventional means, you do not need to use the decode() method because the form is not submitted through the conventional means. The form is submitted asynchronously using Dojo, through Shale Remoting, and it is consumed by the FileUploadHandler.handleFileUpload() method. If the custom component was going to decode the form submission during the Apply Request Values phase, then the same method of looking up the default renderer and delegating the call to the default renderer's decode method would apply.

Method 2: The Custom Renderer Uses a Hidden Child Component and Delegates Rendering to It

The second method to render the component's markup using base renderer functionality also uses a custom tag handler or renderer to decorate the base JavaServer Faces technology component. It then uses the default renderers to render the makeup. This approach is useful for mapping a single parent component to a single child component, but its real strength is mapping a single parent component to multiple child components. Once the mapping of attributes and ValueExpressions to the appropriate child component has taken place, each individual child component's encodeBegin() and endcodeEnd() methods are called in the desired order. The child component's encodeBegin() and endcodeEnd() methods are stored in the parent component's Facet Map, to be retrieved for reuse in future calls.

This approach requires the parent component tag's definition to gather all the information needed to create the child components. The tag attribute data is stored inside the base JavaServer Faces component by using the attributes Map and ValueExpression Map that the component inherited from the UIComponent. By using the parent's Maps directly, the developer can avoid the usual paradigm of convenience accessor and mutator methods that are typically defined in a custom JavaServer Faces component. Components can use this approach when the component developer cannot or does not want to create convenience methods for additional attributes and ValueExpressions on a custom JavaServer Faces component.

The code snippet that follows shows a tag handler that uses the UIComponent's map for storing attribute values and ValueExpressions using this approach, shown in bold type. Note that the FileUpload component that resides in the Java BluePrints Solutions Catalog does not use the hidden child rendering approach, but the following code snippets demonstrate its implementation:

FileUploadTag Code Snippet

public class FileUploadTag 
  extends javax.faces.webapp.UIComponentELTag {

    private javax.el.ValueExpression serverLocationDir=null;

    // Handle unique properties for the FileUpload form.
    public void setServerLocationDir(ValueExpression dir) {
        serverLocationDir=dir;
    }

    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        UIForm outComp=(HtmlForm)component;

        // Pull out the serverLocationDir attribute.
        if (serverLocationDir != null) {
            if (!serverLocationDir.isLiteralText()) {
                 
                   outComp.setValueExpression(                   "serverLocationDir", serverLocationDir);
            } else {
                 
                   outComp.getAttributes().put("serverLocationDir",                   serverLocationDir.getExpressionString());
            }
        }
    }
}
                
 

FileUploadRenderer

In the parent's custom renderer encodeBegin() method, checks are performed to see whether the child component already exists. If the component does not exist, then a child component of the appropriate type is created, stored in the parent component's Facet Map, and updated with the appropriate attributes and ValueExpressions. Once the child component is acquired, the rendering is delegated to the child component's encodeBegin() method.

When the parent's custom renderer encodeEnd() method is called, the child component or components are retrieved from the parent's Facet Map, and rendering is delegated to the child component's encodeEnd() method.

FileUploadRenderer Code Snippet

public void encodeBegin(
  FacesContext context, UIComponent component) 
  throws IOException {

    HtmlForm childComp=(HtmlForm)component.getFacet(CHILD_COMPONENT_ID);

    if(childComp == null) {

        // Create new component and store it for later retrieval.
        childComp=new HtmlForm();
        component.getFacets().put(CHILD_COMPONENT_ID, childComp);

        // Loop through and copy attributes from parent component 
        // to child component.

        Object attr=null;

        for(int ii=0; ii < formAttributes.length; ii++) {
            attr=component.getAttributes().get(formAttributes[ii]);
            if(attr != null) {
                childComp.getAttributes().put(formAttributes[ii], attr);
            }
        }

        // Copy named ValueExpression(s), if necessary.  
        childComp.setValueExpression("serverLocationDir",
             component.getValueExpression("serverLocationDir"));

        // CUSTOM default the HTML enctype so the fileupload 
        // will always work properly.
        childComp.getAttributes().put("enctype", "multipart/form-data");
        ...
    }

    ...
    // Have the child component render itself.
    childComp.encodeBegin(context);
}

public void encodeEnd(FacesContext context, UIComponent component)
  throws IOException
{
    HtmlForm childComp=(HtmlForm)component.getFacet(CHILD_COMPONENT_ID);
    if(childComp != null) {
        // Have the child component render its end.
        childComp.encodeEnd(context);
    }
}

private static String formAttributes[]={"id", "prependId","rendered","accept",
    "acceptcharset","dir","enctype",lang","onclick",ondblclick","onkeydown",
    "onkeypress","onkeyup","onmousedown","onmousemove","onmouseout",
    "onmouseover","onmouseup","onreset","onsubmit","style","styleClass",
    "target","title","binding"};

private static final String CHILD_COMPONENT_ID="bpui_fileupload_childcomponent";
 

As you can see, the approach outlined in Method 2 is more complex but much more powerful. You can use any number of JavaServer Faces component functionalities and wrap them in a parent component for other developers to use. Because this sample of the FileUpload is an AJAX component, the renderer's decode() method is not used because the form is not submitted through the conventional means. The form is submitted asynchronously using Dojo, through Shale Remoting, and the form is consumed by the FileUploadHandler.handleFileUpload() method. If the custom component was going to decode the form submission during the Apply Request Values phase, then the same method of retrieving the child components from the parent's Facet Map and delegating the call to the child's decode method would apply.

Generally speaking, if the child components' decode() methods properly transfer the model data to the parent component, the rest of the JavaServer Faces technology functionality remains the same, despite this hidden child approach. A future article will discuss what the developer should watch out for if the parent component tries to implement complex behavior in the child components -- for example, converters, validators, and events.

For More Information
About the Author

Mark Basler is a senior software engineer who is currently part of the Java BluePrints team that created the Java BluePrints Solution Catalog and Java Pet Store 2.0, reference applications that demonstrate how to design and develop AJAX-enabled Web 2.0 applications. His other contributions include the design and development of key components for Sun's Download Center, eCommerce suites, and Sun Java System Application Server. Before joining Sun, Mark was a consultant on web development, specializing in high-volume ecommerce software. Visit his blog.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.