Creating and Using a Custom Render Kit

 
 
By Roger Kitain and Jennifer Ball, August 2005    
JavaServer Faces technology offers a rich set of components as well as a standard render kit to render these components to an HTML client. At the same time, the design of JavaServer Faces technology is flexible enough to allow component writers to create their own render kit in order to render components to non-HTML clients, whether these components are the standard ones that come with JavaServer Faces technology or if they are custom components that the component writers have created themselves. This document describes how to create your own render kit and how to use a custom render kit in an application both by itself and with other render kits.  It assumes you have some basic knowledge of JavaServer Faces technology, particularly the page life cycle.

 

Steps for Creating and Using a Custom Render Kit


Before going into detail on how to create and use a custom render kit, let's summarize the tasks involved.  The following list outlines the steps required to create a custom render kit and to use its renderers to display components on the target client.
  1. Creating custom component classes or determining which standard components to use
  2. Creating renderer classes to render the components on the target client
  3. Creating a  RenderKit class
  4. Creating a  ResponseWriter class
  5. Creating a  ResponseStateManager class
  6. Creating a  SerializedView class
  7. Creating custom tags and tag handlers for the renderer and component combinations
  8. Registering the render kit, renderers, and components with the application
  9. Using the custom tags in the page

 

Understanding the lifecycle Demo


Scalable Vector Graphics (SVG)XML User Interface Language (XUL)

The lifecycle demo doesn't require the use of XUL; it is there just to demonstrate how easy it is for a JavaServer Faces application to utilize more than one render kit.  In fact, when using releases of JavaServer Faces technology prior to 1.2, you had to implement a custom  ViewHandler class (see Creating a RenderKit Class for more information on this class) to handle multiple render kits, in addition to doing everything outlined in this document.

The lifecycle demo application uses three render kits: the standard HTML render kit, a custom SVG render kit, and a custom XUL render kit. They each render their respective markups. The following diagram shows the flow of the demo between the pages of different markup.

Figure 1: Page Flow of the Demo
 

 

The markup for the first page is produced with the standard HTML render kit, which means it contains HTML markup.  From there, you navigate to the SVG page, which displays a diagram of the JavaServer Faces life cycle phases. Pressing any of the buttons on the lower left corner of the diagram produces an animation that shows how a request flows through each of the phases. The SVG markup for the SVG page is produced from an SVG render kit.

Clicking on any of the life cycle phase boxes causes an HTTP post to the JavaServer Faces controller, which in turn causes the next view to be returned. The response that is returned is the markup and a view identifier for an XUL page.  This page provides more detailed information about the selected phase.

This demo also shows how to deal with the fact that SVG and XUL do not support the notion of an HTTP post mechanism as does HTML.  Instead, the developer must use JavaScript to register an event handler so that when a button is pressed, an event is generated. The JavaScript onclick event handler collects the form input data, builds a post data string, and sends it to the server.

From SVG there are a couple of different ways to post data to a server from JavaScript. This demo relies on browsers that have built-in SVG support, such as in Deer Park Alpha 2, which makes available the XMLHttpRequest object as the posting mechanism.  The demo uses this object to post the request to the JavaServer Faces controller. It also uses this object to handle the response from the JavaServer Faces controller back to the client.

The problem with using the  XMLHttpRequest object is that the response it generates will not include the view identifier of the view to send with the response.  This demo includes an implementation of a  ResponsePhaseListener instance that will access the view identifier from the  FacesContext instance and add it to the response.  This way, the JavaScript callback handling the response knows what view to display next.  The following figure illustrates how this process works.  See Handling Submits Generated from a non-HTML Page for more details on implementing the ResponsePhaseListener class.

Figure 2: Handling Submits Generated from a non-HTML Page

 

We recommend that you download the demo, take a look at the code, and run it. You will need to register at java.net to download the example, but registration is free of charge. To download the example, go to https://javaserverfaces.dev.java.net/ , go to the section that lists the nightly bundles, and follow the instructions for downloading and unpacking the samples bundle.  The lifecycle demo is packaged as the jsf-renderkits.war. This demo runs on Sun's Java System Application Server PE 9.0, code-named glassfish. You can download glassfish from https://glassfish.dev.java.net/.  Please read the README file for information on software requirements and how to deploy and run the demo.  If you would like to see the source code of the demo, you can unpack the WAR file using the command

 jar -xvf jsf-renderkits.war
            

          

The rest of this document describes how to create and use a render kit using the SVG render kit included in the lifecycle demo.

 

Creating the Component Classes or Determining Which Standard Components to Use


UIOutputUICommand

On the other hand, JavaServer Faces technology does not provide any components that represent the shapes that SVG can render.  Therefore, the lifecycle demo needs custom components to represent the shapes that it uses.

If you decide that you do need a custom component, then you must decide whether it should extend a standard component or directly extend  UIComponentBase, the base class for all the standard components.  This base class, along with the standard component classes, are located in the javax.faces.component package and their names begin with UI.

If your custom component serves the same purpose as a standard component, you should extend that standard component rather than directly extend UIComponentBase. For example, the  UIOutput component is intended for displaying something.  Therefore, the  Shape component class used by the lifecycle demo extends  UIOutput rather than  UIComponentBase.

The dynamic lifecycle example defines three component classes: ShapeLine, and Rectangle. The Line and  Rectangle classes both extend  Shape, which makes sense because lines and rectangles are shapes.
The sole purpose of the Line and  Rectangle components is to represent the appropriate shapes on the page; they don't hold any data that needs to be validated, respond to events, or do anything that a component class usually needs to define for the component.   On the other hand, there is a lot of work to be done to render the components, which is what the corresponding renderer classes do.

The job of the  Line and  Rectangle component classes is to identify the component type and the component family for the purpose of registering the components with the application and delegating the rendering of the components to the appropriate renderers.   To delegate rendering, a component class must override the  getFamily method of  UIComponent to return the identifier of a component family.  A component family is used to refer to a component or set of components that can be rendered by a set of renderers.  Here is the  getFamily method from Line.java:

        public String getFamily() {
            

             return (COMPONENT_FAMILY);
            

      }
          
LineCOMPONENT_FAMILY"Line"LineRegistering the Render Kit, Renderers, and Components in the Configuration File

The  Line class also sets a static variable called  COMPONENT_TYPE to  "Line".  This value must match that returned by the getComponentType method of the tag handler that implements the component tag representing the  Line component on the page.  The implementation of the tag handler is described in Creating Custom Tags and Tag Handlers.

The  Rectangle class also implements  getFamily and sets the COMPONENT_TYPE variable to match that defined in the  RectangleTag tag handler class.

The  Shape class does not define a component type or component family; it is merely the base class for  Rectangle and  Line.

After creating the component classes, you can create renderers for them.

 

Creating the Renderer Classes to Render Components on the Target Client


direct implemenationdelegated implementation

By delegating the rendering to a separate renderer, the component makes itself more versatile because multiple renderers would be able to render it to different clients. This is why the custom components of the lifecycle demo all delegate their rendering to separate renderers.  LineRenderer renders the  Line component, and RectangleRenderer renders the  Rectangle component.

The lifecycle demo also provides renderers for the standard components that it uses so that it can render them to the SVG client.  ButtonRenderer renders the  UICommand component as a button.  FormRenderer renders the  UIForm component as an HTML form.  TextRenderer renders the  UIOutput component as a label.

This section uses  LineRenderer and  ButtonRenderer to explain the basic requirements for writing a renderer class for custom and standard components.   At the very least, a renderer class must perform the encoding of a response.  This is the process of generating the markup for the target client.

A renderer class might also need to do some decoding, which involves taking the component's local value from the  request and converting it to a type acceptable to the component class--essentially the reverse of encoding.   A renderer is required to perform decoding only if it needs to retrieve a component's local value or if it needs to queue an event onto the component.

Let's first talk about how to perform encoding.

Performing Encoding

render response

UIComponentBaseencodeBeginencodeChildrenencodeEndstartencodeBeginencodeEndstartendencodeChildren

The lifecycle demo components don't have any child components.  Therefore, rendering these components is a bit simpler.  The renderer class can perform the rendering with its encodeBegin method,  encodeEnd method, or both methods.  In fact, LineRenderer uses both methods:  encodeBegin to render most of the  start tag and  encodeEnd to render the  end tag.  Here are the  encodeBegin and  encodeEnd methods of  LineRenderer:

public void encodeBegin(FacesContext context, UIComponent component)
              

        throws IOException {
              

        if (context == null || component == null) {
              

            // PENDING - i18n
              

            throw new NullPointerException("'context' and/or 'component' is null");
              

        }
              

        if (log.isTraceEnabled()) {
              

            log.trace("Begin encoding component " + component.getId());
              

        }
              

        // suppress rendering if "rendered" property on the component is
              

        // false.
              

        if (!component.isRendered()) {
              

            if (log.isTraceEnabled()) {
              

                log.trace("End encoding component " + component.getId() +
              

                          " since rendered attribute is set to false ");
              

            }
              

            return;
              

        }
              

        
              

        ResponseWriter writer = context.getResponseWriter();
              

               

        writer.startElement("line", component);
              

        writeIdAttributeIfNecessary(context, writer, component);
              

        String x1 = (String)component.getAttributes().get("x1");
              

        if (x1 != null) {
              

            writer.writeAttribute("x1", x1, "x1");
              

        }
              

        String y1 = (String)component.getAttributes().get("y1");
              

        if (y1 != null) {
              

            writer.writeAttribute("y1", y1, "y1");
              

        }
              

        String x2 = (String)component.getAttributes().get("x2");
              

        if (x2 != null) {
              

            writer.writeAttribute("x2", x2, "x2");
              

        }
              

        String y2 = (String)component.getAttributes().get("y2");
              

        if (y2 != null) {
              

            writer.writeAttribute("y2", y2, "y2");
              

        }
              

        String style = (String)component.getAttributes().get("style");
              

        if (style != null) {
              

            writer.writeAttribute("style", style, "style");
              

        }
              

        writer.writeText("\n    ", null);
              

    }
              

               

    public void encodeEnd(FacesContext context, UIComponent component)
              

        throws IOException {
              

        if (context == null || component == null) {
              

            // PENDING - i18n
              

            throw new NullPointerException("'context' and/or 'component' is null");
              

        }
              

        ResponseWriter writer = context.getResponseWriter();
              

               

        writer.endElement("line");
              

        writer.writeText("\n", null);
              

    }
            
Notice that encodeBegin renders the beginning line tag. The encodeEnd method renders the ending line tag.

The encoding methods accept a UIComponent argument and a FacesContext argument. The FacesContext instance contains all the information associated with the current request. The UIComponent argument is the component that needs to be rendered.

The methods render the markup to the ResponseWriter instance, which writes out the markup to the current response. This basically involves passing the HTML tag names and attribute names to the ResponseWriter instance as strings, retrieving the values of the component attributes, and passing these values to the ResponseWriter instance.

The startElement method takes a String (the name of the tag) and the component to which the tag corresponds (in this case, line).  Passing this information to the ResponseWriter instance helps design-time tools know which portions of the generated markup are related to which components.

After calling startElement, the encodeBegin method calls  writeIdAttributeIfNecessary (defined in BaseRenderer), which tries to get the ID of the component in order to write it out.  After rendering the ID, the method calls writeAttribute to render all the tag's attributes. The writeAttribute method takes the name of the attribute, its value, and the name of the corresponding property or attribute of the containing component. The last parameter can be null, and it won't be rendered.

As explained in Creating the Renderer Classes, your renderer might also need to supply a decode method.  The next section describes the  decode method included in  ButtonRenderer.

Performing Decoding

During the  apply request values phase, the JavaServer Faces implementation processes the decode methods of all components in the tree. The decode method extracts a component's local value from incoming request parameters and converts the value to a type that is acceptable to the component class.  The process of

decoding is more bound to the concept of HTTP than it is to a specific rendering technology.

A renderer must implement the  decode method only if it must retrieve the local value or if it needs to queue events. The  decode method of ButtonRenderer does both of these things.  It first retrieves the client ID of the button component that it rendered previously and checks if it matches the client ID for the button the user has just clicked.  If the IDs match, the  decode method calls the component's  queueEvent method to queue on  ActionEvent onto the component.  Here is the decode method from  ButtonRenderer:

 

                   public void decode(FacesContext context, UIComponent component) {
               
        if (context == null || component == null) {            
         throw new NullPointerException("'context' and/or 'component is null");
        }
        if (log.isTraceEnabled()) {
            log.trace("Begin decoding component " + component.getId());
        }
          
        String clientId = component.getClientId(context);
        Map requestParameterMap = context.getExternalContext()
            .getRequestParameterMap();
        String value = (String) requestParameterMap.get(clientId);
        if (value == null) {
            if (requestParameterMap.get(clientId + ".x") == null &&
                requestParameterMap.get(clientId + ".y") == null) {
                return;
            }
        }

        ActionEvent actionEvent = new ActionEvent(component);
        component.queueEvent(actionEvent);

        if (log.isDebugEnabled()) {
            log.debug("This command resulted in form submission " +
                      " ActionEvent queued " + actionEvent);
        }
        if (log.isTraceEnabled()) {
            log.trace("End decoding component " + component.getId());
        }
        return;
    }

            
Note the renderer classes extend BaseRenderer, which in turn extends Renderer. The BaseRenderer class contains definitions of the Renderer class methods so that you don't have to include them in your renderer class.

Now that you have your set of renderers, it's time to add them to your render kit.  Before you do that, you need to create a  RenderKit class.

 

Creating a RenderKit Class


RenderKiRenderKit

As you've guessed by now, a render kit defines a set of renderers that have the ability to render a set of components to one particular kind of client.  For example, this section shows you how to create a render kit for an SVG client.

Much more than just defining a set of renderers, a  RenderKit instance also takes part in rendering the response, re-building the component tree structure, and saving and restoring component state.  It does this in partnership with the default  ViewHandler implementation, which allows applications to control what happens in the restore view and render response phases of the life cycle.

In a nutshell, the life cycle implementation uses  ViewHandler to get information and necessary objects from the render kit so that it can determine whether  ViewHandler should create or restore a view, save or restore state, or render the response, and can instruct  ViewHandler to do one of these things. The primary objects that participate with  ViewHandler in building the view, rendering it, and restoring state are summarized in the following figure.


Figure 3: Objects Used in Rendering
 

One of the objects that ViewHandler obtains from the render kit is the  ResponseStateManager object, which knows the rendering technology used by the render kit and can perform rendering-specific state-management duties.  It might not be immediately obvious what the relationship is between rendering and state-management.  The two are actually related because state is saved on the client by default.  In order to save the state on the client, it must be rendered using the rendering technology specified by the render kit.

Another object the  ViewHandler instance obtains from the render kit is the  ResponseWriter object. It implements methods for writing out markup to a specific client.

Let's take a look at how these objects are used during the life cycle of a JavaServer Faces page.

During the restore view phase of the life cycle,  ViewHandler retrieves the  ResponseStateManager object in order to test if the request is a postback or an initial request.  The  ResponseStateManager object is needed in this case because it is the only one that knows what rendering technology is being used and is therefore the only one that can look at a request, which is rendering-technology specifiec.

If the request is an initial request, the life cycle implementation jumps to the render response phase. During the render response phase, the ViewHandler instance uses the  ResponseStateManager object to save the state of the tree for the benefit of subsequent requests.  Additionally, the  renderView method of  ViewHandler is called.  This method calls encodeAll on the  UIViewRoot instance, which represents the root of the component tree.

The  encodeAll method traverses the tree of components, calling each component's encoding methods. These methods use the component family and renderer type specified in the application's configuration file to find the renderer associated with the component.  Once the renderer is found, it calls the methods of the  ResponseWriter object to render the view.

If a request is a postback, the restoreView method of  ViewHandler is called.  This method uses the ResponseStateManager object to re-build the component tree and restore state.  After the tree is built and state is restored, the ViewHandler instance is not needed until the render response phase occurs again.

Meanwhile, the render kit and renderers are needed to decode the request values during the apply request values phase.  To do this, the  FacesContext instance calls the  processDecodes method on the  UIView instance.  This method traverses the component tree, calling  processDecodes on all the components in the tree.  The components'  processDecodes methods call the associated decoding methods of the renderers.  The renderers then call the methods of the  ResponseWriter object that write out the markup to the client.

Subsequently, the rest of the life cycle phases are executed, including render response, during which the same process occurs as did during the render response phase that was executed for the initial request.

When using JavaServer Faces technology, version 1.2,  you do not have to create a custom  ViewHandler to handle multiple render kits in an application, as you did when using prior versions.  You need only specify the render kit to be used for each view using the  view tag on the corresponding page, as explained in Using the Render Kit in the Page.

Now that you understand the role of the render kit and its ResponseStateManager and  ResponseWriter objects in the life cycle, you're ready to implement a  RenderKit class, which must perform the following tasks:

  • Define  the content types and character encodings that the render kit class supports
  • Implement  the following methods:
    • An  addRenderers method to add renderers to the render kit
    • getRenderer method to retrieve a renderer from the render kit.
    • getResponseStateManager method that returns a  ResponseStateManager instance, which will handle rendering-related state management
    • createResponseWriter that  determines the content type and returns an appropriate  ResponseWriter instance that can generate the appropriate markup.
The first thing to do is to add some private, static variables that define the content type and character encoding that this RenderKit class supports:
        private static String SVG_CONTENT_TYPE = "image/svg+xml";
            

       private static String APPLICATION_XML_CONTENT_TYPE = "application/xml";
            

 private static String TEXT_XML_CONTENT_TYPE = "text/xml";
            

       private static String CHAR_ENCODING = "ISO-8859-1";
            

     private static String CONTENT_TYPE_IS_SVG = "ContentTypeIsSVG";
          
The  RenderKit class will use these variables to test against the current content type and encoding to ensure that they match the ones that this render kit supports.

The JavaServer Faces  implementation invokes the addRenderer method of  RenderKit at application startup time as it processes the application configuration file, in which the render kit and set of renderers are configured.  You'll see in Configuring the Render Kit, Components, and Renderers how to configure the render kit, renderers, and components included in your application.  The  addRenderer method populates a map with the renderer, as shown here:

 public void addRenderer(String family, String rendererType,
            

                            Renderer renderer) {
            

        if (family == null || rendererType == null || renderer == null) {
            

            // PENDING - i18n
            

            String message = "Argument Error: One or more parameters are null.";
            

            message = message + " family " + family + " rendererType " +
            

                rendererType + " renderer " + renderer;
            

            throw new NullPointerException(message);
            

                
            

        }
            

         HashMap renderers = null;
            

             

        synchronized (rendererFamilies) {
            

        if (null == (renderers = (HashMap) rendererFamilies.get(family))) {
            

        rendererFamilies.put(family, renderers = new HashMap());
            

        }
            

            renderers.put(rendererType, renderer);
            

        }
            

    }
          
 

 

The  getRenderer method returns a renderer that can render the components of the specified component family and has the specified renderer type.  The component family is defined in the component class.  The renderer type is defined by the tag handler implementing the tag  that renders the component.
 

    public Renderer getRenderer(String family, String rendererType) {
            

       if (rendererType == null || family == null) {
            

            // PENDING - i18n
            

            String message =
            

             "Argument Error: One or more parameters are null.";
            

            message = message + " family " + family + " rendererType " +
            

                rendererType;
            

            throw new NullPointerException(message);
            

        }
            

       HashMap renderers = null;
            

       Renderer renderer = null;
            

       if (null != (renderers = (HashMap) rendererFamilies.get(family))) {
            

             renderer = (Renderer) renderers.get(rendererType);
            

      }
            

       return renderer;
            

   }
          
 
The JavaServer Faces life cycle implementation invokes the  getResponseStateManager method to retrieve the  ResponseStateManager instance associated with the render kit.  Here is the getResponseStateManager method from  SVGRenderKit:

The following method returns a  ResponseStateManager instance:

    public synchronized ResponseStateManager getResponseStateManager() {
            

        if (responseStateManager == null) {
            

            responseStateManager = new SVGResponseStateManager();
            

        }
            

        return responseStateManager;
            

    }
          
The  createResponseWriter method returns a ResponseWriter instance that provides the content type (in this case image/svg+xml).  The  ViewHandler instance associated with the current view invokes this method during the render response phase to obtain a  ResponseWriter object so that the current view can be written to this object and subsequently rendered to the client.  The developer of the render kit is also responsible for creating the custom ResponseWriter class.

The  createResponseWriter method takes a  Writer object, a  String that contains a list of content types, and the character encoding, all of which are used to render the response.  The method first loads the supported content types defined by the  RenderKit class into an array.  The method then checks if the  String of content types passed to the method is  null.  If it is, the method looks for a content type in the context representing the response and in the request header.  If the method finds a content type, it looks for a matching content type in the array of supported types.  If no match is found, the content type is set to SVG.  The method then returns a new  SVGRenderKit instance with the specified writer, and an acceptable content type and character encoding.

 

Creating a ResponseWriter Class


The  ResponseWriter class defines methods for rendering to the target client.  A  ResponseWriter object is used by the  renderView method of  ViewHandler to render the view during the render response phase of the life cycle.

The default  ResponseWriter that comes with JavaServer Faces technology extends the  Writer class from the Java 2 Standard Edition to add special methods for producing elements and attributes for markup languages, such as XML and HTML.  It also defines the content type for the render kit that creates it.

Because SVG is derived from XML,  SVGResponseWriter pretty much re-uses the code from the default response writer class.  The only real difference is that  SVGResponseWriter defines a content type of SVG, as shown by this line of code from  SVGResponseWriter:

    private String contentType = "image/svg+xml";
If you are creating a custom render kit and the client for which it defines renderers is XML-based then you will be able to simply copy over the  SVGResponseWriter code to your  ResponseWriter class.

 

Creating a ResponseStateManager Class


Creating a RenderKit ClassResponseStateManagerResponseStateManager

Creating a  ResponseStateManager class involves providing implementations for the following methods:
 

  • An  isPostBack method that returns  true if the current request is a postback. The  ResponseStateManager makes this determination by checking if it has already written out the component tree state during a previous request.
  • getState method that retrieves the component tree state from the current request.  This method is called by the  restoreView method of  ViewHandler.
  • writeState method that writes out the state to the client.  This method is called by the  renderView method of  ViewHandler during the render response phase.  Its purpose is to write out a serialized state of the component tree using a  SerializedView object.  SerializedView is  a serializeable object that contains the structure and state of the component tree.  See Creating a SerializedView Class for more information.  The  writeState method receives either a  SerializedView object or a two-element object array containing the structure of the component tree and its state, respectively.  If the argument is not a  SerializedView object,  it will create a  SerializedView object from the array.  In either case, the method calls another  writeState method that takes the serialized view and does the work of writing out the state of the tree.


Much of the  SVGResponseStateManager class is copied directly from the ResponseStateManagerImpl class that is part of the JavaServer Faces reference implementation.  The  writeState method is the only one that has code that is specific to rendering SVG.

More specifically, the  writeState method does the following:

  • Gets the  ResponseWriter instance associated with this render kit.
  • Gets the component tree state that this  ResponseStateManager instance had saved previously.
  • Determines if the application is configured to save state in the client (which is the default)
  • If the application is performing client-side state saving, compresses the state and secures it, and writes out the state to the client in a hidden field using the  ResponseWriter instance.
  • If the application is performing server-side state saving, writes out the tree structure.
  • Finally, gets the ID of the render kit (which is SVG in this case), and renders it to a hidden field.
SVGResponseStateManager.java

 

Creating a SerializedView Class


writeStateResponseStateManagerSerializedViewSerializedViewSerializedView
public class SerializedView extends Object implements Serializable {
            

   private Object structure = null;
            

   private Object state = null;
            

             

   public SerializedView(Object newStructure, Object newState) {
            

      structure = newStructure;
            

      state = newState;
            

   }
          
   public Object getStructure() {
            

      return structure;
            

   }
          
   public Object getState() {
            

      return state;
            

   }
            

}
            

             

          

 

Registering the Render Kit, Renderers, and Components in the Configuration File


faces-config.xml

The following piece of the lifecycle demo's configuration file show's how to register the SVG render kit, the  Line component, the LineRenderer renderer, and the  ButtonRenderer renderer, which renders a  UICommand component to an SVG client.  The rest of the configuration information is ommitted. Please refer to the lifecycle demo's  faces-config.xml file to find out how the other components and renderers are configured.
 

  ...
            

  <component>
            

      <component-type>Line</component-type>
            

      <component-class>
            

   renderkits.components.svg.Line
            

      </component-class>
            

  </component>
            

  ...
            

  <render-kit>
            

    <render-kit-id>SVG</render-kit-id>
            

    <render-kit-class>
            

        renderkits.renderkit.svg.SVGRenderKit
            

    </render-kit-class>
            

    ...
            

    <renderer>
            

      <component-family>
            

  javax.faces.Command
            

      </component-family>
            

      <renderer-type>
            

      renderkit.svg.Button
            

      </renderer-type>
            

      <renderer-class>
            

       renderkits.renderkit.svg.ButtonRenderer
            

      </renderer-class>
            

    </renderer>
            

    <renderer>
            

      <component-family>Line</component-family>
            

      <renderer-type>
            

     renderkit.svg.Line
            

      </renderer-type>
            

      <renderer-class>
            

 renderkits.renderkit.svg.LineRenderer
            

      </renderer-class>
            

    </renderer>
            

  </render-kit>
          
componentrender-kitcomponent-familyrenderer-typerenderercomponent-familygetFamilyrenderer-typegetRendererType

 

Creating Custom Tags and Tag Handlers


rendering of that componentLineTag.java

To implement a tag handler, you need to create a class that extends  UIComponentELTag and add the following to it:

  • Properties to access the values for all of the component and renderer attributes
  • setProperties method that passes the value of tag attributes to the component
  • getRendererType method that returns the type of the renderer that renders this tag
  • getComponentType method that returns the type of the component that this tag represents
  • release method that releases resources
The  LineTag tag handler has several properties that are called by the life cycle implementation to set the component's attribute values to those supplied as tag attributes in the page.  The following property is used to access the value of the  Line component's  onClick attribute:
    // PROPERTY: onclick
            

    private javax.el.ValueExpression onclick;
            

    public void setOnclick(javax.el.ValueExpression onclick) {
            

        this.onclick = onclick;
            

    }
          
Notice that the property (and all of the properties in this tag handler) accepts a  ValueExpression instance.  This is because the attributes of  Line component only accept value expressions.

To pass the values of the tag attributes to  Line component, the tag handler implements the  setProperties method.  The following lines set the value on the  onClick property:

       if (onclick != null) {
            

            if (!onclick.isLiteralText()) {
            

                line.setValueExpression("onclick", onclick);
            

            } else {
            

                line.getAttributes().
            

                   put("onclick", onclick.getExpressionString());
            

            }
            

        }
          
The tag handler also needs to retrieve the types of the component and renderer that the tag represents on the page.  It does this with the  getComponentType and  getRendererType methods:
    public String getRendererType() {
            

        return "renderkit.svg.Line";
            

    }
            

             

    public String getComponentType() {
            

        return "Line";
            

    }
          
The types that these methods return must match those under which the component and renderer are registered with the configuration file as shown in  Registering the Render Kit, Components, and Renderers in the Configuration File.

Finally, it's recommended that all tag handlers implement a release method, which releases resources allocated during the execution of the tag handler. The release method of LineTag is as follows:

public void release() {
              

        super.release();
              

               

        // rendered attributes
              

        this.onclick = null;
              

        this.onfocusin = null;
              

        this.onfocusout = null;
              

        this.onmousedown = null;
              

        this.onmousemove = null;
              

        this.onmouseout = null;
              

        this.onmouseover = null;
              

        this.onmouseup = null;
              

        this.style = null;
              

        this.x1 = null;
              

        this.y1 = null;
              

        this.x2 = null;
              

        this.y2 = null;
              

               

            
Now that the tag handler, component, and renderer are defined, the next step is to define the actual tag in a tag library descriptor (TLD).  The web container uses the TLD to validate the tag.  The set of tags that are part of the standard HTML render kit are defined in the  html_basic TLD.

The custom tags used to render SVG components for the lifecycle demo are defined in the  svg.tld file.  The following code snippet is part of the  line tag's definition.  The definition must have the following sub-elements:

  • name element that defines the name of the tag
  • tag-class element that specifies the tag handler class
  • body-content element specified as JSP
  • An  attribute element for each attribute of the  line tag
  • rendered element that indicates whether or not the component should be processed during all phases of the life cycle or only during render response.  The default is  true, meaning that the element should be rendered during all phases.
Every  attribute element must have a  name element defining the name of the attribute and a  required element defining if the attribute is required for every instance of the tag.

All  attribute elements except the one defining the  id attribute must also include a  deferred-value element that specifies what type of value that the attribute accepts.

    <tag>
            

       <name>line</name>
            

       <tag-class>
            

 renderkits.taglib.svg.LineTag
            

       </tag-class>
            

    <body-content>JSP</body-content>
            

        <attribute>
            

             ...
            

             <name>id</name>
            

         <required>false</required>
            

              <rtexprvalue>false</rtexprvalue>
            

             

        </attribute>
            

    <attribute>
            

             <name>rendered</name>
            

           <required>false</required>
            

              <deferred-value>
            

                        <type>boolean</type>
            

            </deferred-value>
            

       </attribute>
            

    <attribute>
            

              ...
            

            <name>x1</name>
            

         <required>false</required>
            

              <deferred-value>
            

                        <type>java.lang.String</type>
            

           </deferred-value>
            

       </attribute>
            

     ...
            

    </tag>
            

             

          

 

Using the Render Kit and Associated Tags in the Page


  • page directive that indicates the content type of the page, which matches the content type defined by the render kit's  ResponseWriter object.
  • Tag library directives declaring the tag libraries that define the custom component tags that can display the components rendered by your custom render kit
  • A tag that indicates the rendering technology that the page uses (SVG, in this case)
  • renderKitId attribute on the  view tag.  This attribute holds the ID of the render kit.
svg.jsp
<%@ page contentType="image/svg+xml"%>
            

<%@ taglib uri="#" prefix="g" %>
            

<%@ taglib uri="#" prefix="f" %>
            

                                                                                      
            

<svg xmlns="http://www.w3.org/2000/svg"
            

     xmlns:xlink="http://www.w3.org/1999/xlink">
            

    <style type="text/css">
            

      rect:hover {fill-opacity:0.3;}
            

    </style>
            

    <f:view renderKitId="SVG" > <g:form id="form">
            

           <g:outputText x="100" y="50" textAnchor="middle"
            

         value="JSF Request Processing Lifecycle"
            

                style="stroke:black;
            

                stroke-width:0.5;
            

          fill:none; font-size:32pt;" />
            

             

           <!-- Restore View Graphic -->
            

             

           <g:line id="toRestore" x1="25" y1="125"
            

           x2="100" y2="125"
            

          style="stroke:black; fill:none;" />
            

           <g:commandButton id="restore" width="120"
            

                height="50" x="100" y="100" type="submit"
            

          action="xul-restore"
            

               style="stroke:black; fill:#8470ff;" >
            

              <g:outputText x="130" y="120"
            

                textAnchor="middle" value="Restore" />
            

              <g:outputText x="135" y="140"
            

               textAnchor="middle" value="View" />
            

           </g:commandButton>
            

...
            

</g:form>
            

</f:view>
            

</svg>
            

             

          

 

Handling Submits Generated from a non-HTML Page


XMLHttpRequest

For this reason, the lifecycle demo requires a  ResponsePhaseListener implementation to get the view ID after the invoke application phase and before the render response phase.  When the ResponsePhaseListener object is notified that the invoke application phase has occurred, it retrieves the view ID from the  FacesContext instance and adds it to the URL to be used to render the next page.  It then adds this URL to the response header so that the next view can be rendered in the subsequent render response phase.

The following code sample shows the lifecycle demo's ResponsePhaseListener.afterPhase method, which the JavaServer Faces life cycle implementation calls after the invoke application phase.  This method has the task of appending the URL of the new view to the response.

    public void afterPhase(PhaseEvent event) {
            

        // Disregard requests that are not XMLHttpRequest(s)
            

        Map requestHeaderMap =
            

           event.getFacesContext().getExternalContext().
            

            getRequestHeaderMap();
            

        if (requestHeaderMap.get(XML_HTTP) == null) {
            

            return;
            

        }
            

        // If we're dealing with an XMLHttpRequest...
            

        // Get the URI and stuff it in the response header.
            

        FacesContext context = event.getFacesContext();
            

        String viewId = context.getViewRoot().getViewId();
            

        String actionURL =
            

         context.getApplication().getViewHandler().
            

                  getActionURL(context, viewId);
            

        HttpServletResponse response =
            

          (HttpServletResponse)context.getExternalContext().
            

                  getResponse();
            

        response.setHeader("Cache-Control", "no-cache");
            

        response.setHeader(VIEW_URI, actionURL);
            

    }
          
AJAX entries

 

Conclusion


 

Resources




JavaServer Faces Technology
AJAX with the Java 2 Enterprise Edition
The Glassfish Project
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.
Left Curve
Java SDKs and Tools
Right Curve
Left Curve
Java Resources
Right Curve

Java 8 banner (182)



Java One 2014 RHS Banner

Duke's Choice RHS Banner