Extending Default JSF Renderer Functionality

Mark Basler
Status: In Early Access

Problem Description

When designing JSF components, developers have to weigh a variety of options.  One of which, is how will the component render its markup.  If the component being developed has new functionality that isn't closely related to other existing components then the usual methodology would be to develop a new renderer to generate the targeted markup.  This is a straight forward approach that is detailed in the JavaEE5 Tutorial under the "Creating Custom UI Components" section. 

In some cases, the component to be created adds functionality to a component that already exists, like when AJAX functionality is added.  A developer wouldn't want to re-implement the base rendering behavior of the entire component.  This entry explores alternative methods to render component's markup, utilizing the base rendering functionality that already exists.

Solution

In the cases where the component to be created adds functionality to a component that already exists, like the AJAX FileUpload JSF component, base renderer functionality can be utilized to render the targets markup.  This FileUpload component was designed to add AJAX functionality to the JSF javax.faces.component.UIForm component.  Since the markup to be rendered for the FileUpload component would be the same as the UIForm with some default attributes set, it wouldn't make sense to code the renderer from scratch. Given the Java developers object oriented mentality, it would make sense to extend the base functionality,  adding only the functionality specific to the AJAX FileUpload Component. 

Since the component is to be used with JSPs then the new component will still need a separate JSP tag handler that exposes the component's attributes.  The tag handler  also links the JSF component-type with the renderer-type.  There currently isn't a non-vendor specific way to extend the base tag handler's functionality, but soon tools may be available to help create these files automatically.  Detail on how to create the custom JSP tag library descriptor and customer tag handler are out of the scope  of this entry, but these tasks are well documented in the JavaEE5 Tutorial, under the "Creating the Component Tag Handler" section.

Below we discuss the different methods that can be used to render component's markup using base renderer functionality.

Custom Renderer calls default renderer for base functionality

Using a custom renderer to call a default component renderer can make it very easy to add functionality that doesn't alter the way the base component is rendered.  The AJAX FileUpload Component's custom tag adds & removes some attributes that were available in the default JSF form tag.  The removed attributes were set to required values in the FileUpload's custom renderer.  The added attributes were set as hidden fields to be sent when the form was submitted.  Once the appropriate values are populated in the HtmlForm component, then the default renderer is retrieve through the FacesContext.  The default renderer's encodeBegin & encodeEnd methods are called, passing in the HtmlForm component that has been updated to hold the FileUpload's specific values.  The default renderer renders the component just as it would have if the customer tag/renderer weren't used. 

Below are a code snippets that demonstrate this approach.  Simple alterations were made in the coding examples for clarity.

FileUpload Custom Tag artifacts:

The tag handler for the FileUpload tag links the base JSF component ("javax.faces.HtmlForm") with the custom renderer ("FileUploadForm") that is defined in the FileUpload faces-config file, along with the component-family.

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

public String getRendererType() {
return ("FileUploadForm");
}
...
}

faces-config.xml
    <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>

FileUploadRenderer:

The FileUploadRenderer.encodeBegin method sets HtmlForm default attributes, lookups up the default renderer for the "javax.faces.Form" component-family & renderer-type, then delegate the rendering to the base renderer's encodeBegin method, using the modified component.  The rendering of the FileUpload component's children are sent to self render, so no action is necessary in the FileUploadRenderer. The FileUploadRenderer.encodeEnd method writes out hidden fields into the form's body 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 default renderer for 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 were set by the specific taghandler
String serverLocationDir=(String)component.getAttributes().get("serverLocationDir");

// add hidden field to represent location of directory on server
// if doesn't exist, defaulted in FileUploadHandler
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 default renderer for form render all the basic form attributes
Renderer baseRenderer=context.getRenderKit().getRenderer("javax.faces.Form", "javax.faces.Form");
baseRenderer.encodeEnd(context, component);
}

As you can see this is a straight forward approach to customizing functionality of existing components without having to re-implement the default renderer's functionality.  Since the FileUpload is an AJAX component, the renderer's decode method isn't used because the form isn't submitted through the conventional means.  The form is submitted asynchronously using Dojo, through Shale-Remoting and 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.

Custom Renderer uses a hidden child component and delegates rendering to it

This method also uses a custom tag handler/renderer to decorate the base JSF component(s) and then utilizes the default renderers to render the makeup.  This approach will work 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/ValueExpressions to the appropriate child component has be performed, each individual child component's encodeBegin/endcodeEnd methods are called in the desired order.  The child component's are stored in the parent component's Facet Map to be retrieved for reuse in future calls. 

This approach requires that all the information that is needed to create the child components is gathered by the Parent component's tag definition.  The tag attribute data is stored inside the base JSF component by using the component's attribute Map and ValueExpression Map that is inherited from the UIComponent.  Using the parent's Maps directly, side-steps the usual paradigm of convenience accessor and mutator methods that are usual defined in a custom JSF component.  This approach can be used by components where the component developer doesn't want to or can't create convenience methods for additional attributes/ValueExpressions on a custom JSF component.  Depicted below is a tag handler that uses the UIComponent's map for storing attribute values and ValueExpressions using this approach (in BLUE).

The current FileUpload component doesn't use the hidden child rendering approach, but below are the relevant code snippets demostrating its implementation:

FileUploadTag Code Snippet:

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

private javax.el.ValueExpression serverLocationDir=null;

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

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

// pull out 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 preformed to see if the child component have already been created.  If the component doesn't 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/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(s) 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 main 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 this approach is more complex, but much more powerful.  You can utilize any number of JSF component's functionality and wrap it in a parent component for use by others.  Since this mock up of the FileUpload is an AJAX component, the renderer's decode method isn't used because the form isn't submitted through the conventional means.  The form is submitted asynchronously using Dojo, through Shale-Remoting and 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 decode method's of the child components properly transfer the model data to the parent component the rest of the JSF functionality would remain the same, irrelevant of this hidden child approach.  There may be caveats if the parent component tries to implement complex behavior in the child components (e.g. converters, validators and events), but I will leave that for the next version of the entry.   
© Sun Microsystems 2006. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.