Previous Section  < Day Day Up >  Next Section

13.1 Developing Custom Renderers

Developing a custom renderer is relatively simple. All renderers extend an abstract class called javax.faces.render.Renderer. Subclasses of this class are attached to components and are responsible for rendering (or encoding, as it's called in the specification) the component and, if it's an input component, for decoding the submitted value to the component's internal representation.

A component is equipped with a renderer of a specific type by calling the component's setRendererType( ) method. The renderer type is an identifier that's unique per component family. Each component belongs to a component family, which identifies the basic nature of a component, e.g., that it's an output component or a command component. Which renderer class to use for a component is determined by the combination of the renderer type the component is equipped with and the family it belongs to. Picking a renderer class based on the combination of the two IDs makes it possible to use the same, intuitive renderer type IDs for renderer classes with widely different behavior, each class registered for a different component family. For instance, the renderer type ID javax.faces.Link combined with the javax.faces.Output family can identify a renderer class that renders a regular HTML link element with the component's value as the URL, and the same renderer type ID combined with the javax.faces.Command family can identify another renderer class that renders an HTML link element with JavaScript code for submitting the form the component belongs to and with the component's value as the link text. Without the component family concept, one renderer class would have to support both types of components or renderer type IDs would have to include something similar to a family ID in their names, which would lead to namespace scalability problems in the long run.

For the JSP layer, a custom action represents the combination of a component type and a renderer type, so you must implement a custom action tag handler for the combination of a standard component and your custom renderer as well if you want it to be easy to use in a JSP page.

13.1.1 A Renderer for Encoding Only

Let's start with a renderer that only deals with encoding—no decoding. The only standard renderer for the UIData component (i.e., the component that represents tabular data) is a renderer that represents the component as an HTML table. Quite often, though, an application needs to render tabular data horizontally: for instance, as a navigation bar with page number links to individual pages in a large query result, as shown in Figure 13-1.

Figure 13-1. Reports lists area with page navigation links
figs/Jsf_1301.gif

Figure 13-1 shows a new version of the reports list area from the sample application with a page links bar in addition to the first, previous, next, and last page button we used earlier.

To develop and use this custom renderer, there are three things you must do: implement the renderer class, register the renderer class with JSF, and implement a JSP custom action that represents a UIData component equipped with the custom renderer.

The new component/renderer combination bound to a data model containing beans representing each report list page can then be added to the original report list area page to renderer the page links bar. Let's take it step by step, starting with the renderer class.

13.1.1.1 The Renderer class

The links in Figure 13-1 are generated with a custom renderer for the UIData component named com.mycompany.jsf.render.BarRenderer. Just as for the standard table renderer, the bar renderer uses UIColumn children components to represent columns of the tabular data, often a single column child containing a command component with a link renderer for processing a single table column. The bar renderer iterates through the model rows and renders its column children. Unlike the standard table renderer, which nests its children within a <tr> element for each row and <td> elements for each column, the bar renderer doesn't add any markup elements on its own, so you have full control over the generated content.

The BarRenderer class declaration and the first public method look like this:

package com.mycompany.jsf.renderer;



import java.io.IOException;

import java.util.Iterator;

import java.util.List;



import javax.faces.context.FacesContext;

import javax.faces.context.ResponseWriter;

import javax.faces.component.UIColumn;

import javax.faces.component.UIComponent;

import javax.faces.component.UIData;

import javax.faces.component.UIViewRoot;

import javax.faces.render.Renderer;



public class BarRenderer extends Renderer {



    public boolean getRendersChildren( ) {

        return true;

    }

The BarRenderer class extends the abstract javax.faces.render.Renderer class and overrides the inherited implementation of the getRendersChildren() method to return true instead of false. This is a requirement for a renderer that controls in detail how its children are rendered. The same method exists in the UIComponent class, but a component configured with a renderer delegates the getRendersChildren() method call to its renderer by default.

Let's pause for a moment and discuss why getRendersChildren() method is needed. If the response to a JSF request was composed solely from JSF components, it could be created simply by asking the root component to recursively render the complete tree. What complicates matters is the fact that, with the JSP layer, the response is composed by combining JSF-generated content with template text and non-JSF action content from the JSP page. Consider this JSP page snippet:

<h:form>

  Label: <h:inputText value="#{myBean.myProperty}" />

</h:form>

When the JSP container processes this page, it invokes the doStartTag() method on the tag handler for the <h:form> action, writes the "Label:" template text to the response, invokes the doStartTag() and the doEndTag() methods on the tag handler for the <h:inputText> action, and invokes the doEndTag() method on the tag handler for the <h:form> action. Unless we're careful here, the template text ends up before the <form> and <input> elements generated by the JSF components. To avoid this, the JSF rendering API defines both an encodeBegin() method and an encodeEnd( ) method. For a component that doesn't render its children, like the UIForm component, the tag handler's doStartTag() method calls encodeBegin(), which writes the <form> start tag to the response. The start tag therefore comes before the template text added to the response by the JSP container. The doStartTag() method for the <h:inputText> action then writes the single tag for the <input> element, and the doEndTag() method for the <h:form> action adds the end tag for the <form> element.

So far so good, but this doesn't work for a component that needs full control over how its children are rendered, like the UIData component. Besides calling the encode methods of the component, a tag handler also creates the component it represents and adds it as a child of the component represented by its parent tag handler. The table renderer for the UIData component uses the children's facets to render the table header and footer elements, and then the children's children to render columns for each row in its data model. Hence, all children must be available already when the UIData encodeBegin() method is called, and the tag handlers for the children must not invoke any encode methods on the children. The bar renderer we develop in this section is simpler, but it also need to control how the children are rendered.

To tell the tag handlers when to call the encode methods and which methods to call, the component or its renderer returns true or false from the getRendersChildren() method. The tag handler must first call the getRendersChildren() method to decide which of the rendering methods to call. If the method return true, it must call encodeBegin( ), encodeChildren(), and encodeEnd() in the doEndTag() method and it must not call any encoding methods on the component's children; the component does that itself in its encodeChildren( ) method instead. If getRendersChildren() returns false, only encodeBegin() and encodeEnd() must be called (in doStartTag() and doEndTag(), respectively) and all child components must be asked to render themselves individually.[1] As with the getRendersChildren() method, all encoding methods are part of the UIComponent interface, but a component configured with a renderer delegates the calls to its renderer.

[1] The same rules applies to any type of class that asks a component to render itself, such as those used with a custom ViewHandler, as described in Chapter 15.

A renderer may split its work between the three rendering methods, as I mentioned. For instance, a renderer that renders a markup element for itself with the children rendered as nested elements may render the start tag in encodeBegin() and the end tag in encodeEnd(), and if it renders its children, render the children's elements in encodeChildren(). For the BarRenderer, I decided to implement all rendering in the encodeChildren() method, which looks like this:

public void encodeChildren(FacesContext context, UIComponent component)

    throws IOException {



    if (!component.isRendered( )) {

        return;

    }



    String clientId = null;

    if (component.getId( ) != null && 

        !component.getId( ).startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {

        clientId = component.getClientId(context);

    }



    ResponseWriter out = context.getResponseWriter( );

    if (clientId != null) {

        out.startElement("span", component);

        out.writeAttribute("id", clientId, "id");

    }



    UIData data = (UIData) component;



    int first = data.getFirst( );

    int rows = data.getRows( );

    for (int i = first, n = 0; n < rows; i++, n++) {

        data.setRowIndex(i);

        if (!data.isRowAvailable( )) {

            break;

        }



        Iterator j = data.getChildren( ).iterator( );

        while (j.hasNext( )) {

            UIComponent column = (UIComponent) j.next( );

            if (!(column instanceof UIColumn)) {

                continue;

            }

            encodeRecursive(context, column);

        }

    }



    if (clientId != null) {

        out.endElement("span");

    }

}

The encodeChildren() method first checks the value of the component's rendered property. All components have this property, and it can be set to false to temporarily hide a component without removing it from the view. If the rendered property is false, the encodeChildren( ) method returns immediately.

Next comes a bit of code for figuring out if the component has an ID, and if so, whether it's assigned by the page author or automatically assigned. If the ID starts with a character defined by the UIViewRoot.UNIQUE_ID_PREFIX constant (an underline character), it must be automatically assigned, because the JSP component actions are required to assign an ID with this prefix for components without an id attribute, and the id attribute value a page author assigns is not allowed to start with this character. The point of these rules is to ensure that an explicitly assigned IDs is always reflected in the generated markup so it can be used by client-side scripting code to access the markup element for the component.

The encodeChildren() method calls the component's getClientId() method to get an ID value suitable for use in the generated markup if the ID is explicitly assigned. The client ID may be exactly the same as the component ID, but if the component belongs to a naming container, the ID is adjusted by prepending the component IDs for all parent naming containers, with colons separating the individual IDs. For instance, the client ID for a component with the ID input in a form with the ID myForm is myForm:input, because the form component is a naming container. The renderer also gets a chance to convert the client ID in other ways, e.g., encode special characters, so the only sure way to know what the client ID happens to be for a component is to call the getClientId() method.

The convention established by the standard JSF HTML renderers is to generate a <span> element with an id attribute when the page author has explicitly assigned an ID. The exception is if there's another HTML element that represents the complete component, such as a <table> element, in which case, the HTML element is used for the ID instead of a <span> element. The encodeChildren( ) method uses the javax.faces.context.ResponseWriter class to generate the <span> element. As you may recall from Chapter 6, the ResponseWriter is an extension of java.io.Writer with methods for writing markup language elements, such as HTML and XML elements. The encodeChildren() method calls the startsElement() and writeAttribute( ) methods to write the <span> element start tag with the id attribute with the client ID as the value. Both of these methods take an extra argument over what's absolutely needed to write the start tag and the attribute: the startElement() method takes a reference to the component the element is generated for and the writeAttribute() method takes the name of the component property or attribute the markup element attribute value comes from. These values can be used by a custom ResponseWriter provided by a JSF development tool to keep track of how the generated markup corresponds to the components in the view. The default ResponseWriter doesn't do anything with this information, but you should still provide it to help tools that do.

After writing the <span> element, it's time to generate the HTML elements for the children. Two UIData properties controls which rows to render. The first property holds the index of the first row to render and the rows property holds the number of rows to render. The encodeChildren( ) method loops over the rows from the UIData component's model using these property values as the initial loop variable value and in the loop end condition, respectively. For each processed row, encodeChildren() calls the UIData setRowIndex() method with the current index to position the model at the current row and expose it to the component's children through the variable named by the var property. The isRowAvailable() returns true if there's a row that corresponds to the current index. If isRowAvailable() returns false, there are less rows in the table model than the maximum specified by the rows property, so the encodeChildren() method exits the loop.

As long as a row is available, all the UIData children are asked to render themselves. The children are bound typically to properties of the current row object exposed through the var variable so the result is different for each row. Just as for the standard table renderer, the bar renderer processes children only of type UIColumn. The actual rendering is handled by the private encodeRecursive( ) method, which we'll look at in a bit. Finally, if a <span> element start tag was rendered earlier, the end tag is generated after rendering all children.

The encodeRecursive() method looks like this:

    private void encodeRecursive(FacesContext context, UIComponent component)

        throws IOException {



        if (!component.isRendered( )) {

            return;

        }



        component.encodeBegin(context);

        if (component.getRendersChildren( )) {

            component.encodeChildren(context);

        } else {

            Iterator i = component.getChildren( ).iterator( );

            while (i.hasNext( )) {

                UIComponent child = (UIComponent) i.next( );

                encodeRecursive(context, child);

            }

        }

        component.encodeEnd(context);

    }

}

First, encodeRecursive() checks if the child component should be rendered at all and returns if not. It then calls the encodeBegin() method. If the component renders its children, it calls the encodeChildren( ) method. Otherwise, the encodeRecursive( ) method iterates through all the component's children and calls itself recursively for each child. Finally, it calls the encodeEnd() method.

When the encodeChildren() method returns, a <span> element with the UIData component's client ID has been rendered if an explicit ID is specified, containing whatever the UIData child components render for each row in the table. With a single UIColumn child containing a command component with a link renderer, the result is a list of HTML <a> elements with JavaScript code for submitting the form the UIData component belongs to, as shown in Figure 13-1.

13.1.1.2 Registering the renderer

All pluggable classes in JSF must be registered under a unique name in the faces-config.xml class, so that other classes can create instances of these classes. A renderer is a pluggable class, and here's how you register it:

<faces-config>

  ...

  <render-kit>

    <renderer>

      <component-family>javax.faces.Data</component-family>

      <renderer-type>com.mycompany.Bar</renderer-type>

      <renderer-class>

        com.mycompany.jsf.renderer.BarRenderer

      </renderer-class>

    </renderer>

    ...

  </render-kit>

  ...

</faces-config>

Renderers are grouped into render kits declared by a <render-kit> element. Within this element, you can optionally have a <render-kit-id> element that gives the render kit a unique ID within the application. An application can then use a <default-render-kit-id> element (see Appendix E for details) to say that it wants to use the custom kit with this ID instead of the default HTML kit. An example is a render kit with WML renderers if you're developing an application for WAP clients. If you omit the <render-kit-id> element, as I do here, the renderer is added instead to the default HTML render kit.

Within the kit, each renderer is identified by two pieces of information: a component family and a renderer type, declared by the <component-family> and <renderer-type> elements. As I mentioned earlier, a component family identifies the type of component the renderer belongs to, such as a command component or an input component. All standard JSF classes use component family names composed from the javax.faces prefix followed by the component class name minus the "UI" class name prefix, e.g., javax.faces.Data for the UIData component. The renderer type ID must be unique per component family. All standard JSF renderer type IDs are also prefixed with javax.faces. I follow the same convention for the custom bar renderer, giving it the renderer type ID com.mycompany.Bar, but you can use any name as long as it's unique within the component family.

13.1.1.3 The JSP tag handler class

In order to use a UIData component with the custom renderer in a JSP page, you must develop a JSP tag handler that creates and configures an instance of the component and tells it to use the BarRenderer class for rendering. It's easier than it sounds, because a base class called javax.faces.webapp.UIComponentTag handles most of the tricky details. Here's the complete com.mycompany.jsf.taglib.DataBarTag class:

package com.mycompany.jsf.taglib;



import javax.faces.webapp.UIComponentTag;

import javax.faces.component.UIComponent;

import javax.faces.component.UIData;

import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;



public class DataBarTag extends UIComponentTag {



    private String first;

    private String rows;

    private String value;

    private String var;



    public void setFirst(String first) {

        this.first = first;

    }



    public void setRows(String rows) {

        this.rows = rows;

    }



    public void setValue(String value) {

        this.value = value;

    }



    public void setVar(String var) {

        this.var = var;

    }



    public String getComponentType( ) {

        return "javax.faces.Data";

    }



    public String getRendererType( ) {

        return "com.mycompany.Bar";

    }



    protected void setProperties(UIComponent component) {

        super.setProperties(component);



        FacesContext context = getFacesContext( );

        if (first != null) {

            if (isValueReference(first)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(first)

                component.setValueBinding("first", vb);

            } else {

                ((UIData) component).setFirst(Integer.parseInt(first));

            }

        }



        if (rows != null) {

            if (isValueReference(rows)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(rows)

                component.setValueBinding("rows", vb);

            } else {

                ((UIData) component).setRows(Integer.parseInt(rows));

            }

        }



        if (value != null) {

            ValueBinding vb = 

                context.getApplication( ).createValueBinding(value)

            component.setValueBinding("value", vb);

        }

                

        if (var != null) {

                ((UIData) component).setVar(var);

        }

    }

}

The class is called DataBarTag, following the standard JSF convention of using the combination of the component type and the renderer type IDs as the custom action name, and the JSP convention of using the action name plus a "Tag" suffix for the tag handler class. The tag handler class extends UIComponentTag and supports four custom action attributes: first, rows, value, and var. Each attribute is backed by a private instance variable and a setter method, called by the JSP container when it processes the page.

The UIComponentTag calls the getComponentType() and getRendererType() methods to know which type of JSF component to create and which type of renderer to configure it with. All you need to do is ensure that these two methods return the desired type IDs. The DataBarTag class returns javax.faces.Data as the component type and com.mycompany.Bar as the renderer type. The UIComponentTag creates an instance of the class mapped to the returned component type and adds it to the component tree for the view when the JSP page with this custom action is processed the first time.

The UIComponentTag then calls the setProperties() method. The implementation of this method in the DataBarTag class calls super.setProperties() to let the parent class do the default processing, which includes configuring the component with the renderer type returned by the getRendererType( ) method. The UIComponentTag class implementation of the setProperties() method also sets the component properties supported by all components—id, binding, and rendered—and provides the tag handler setter methods for these properties; all you need to do to support these properties is declare that your subclass accepts them in the Tag Library Descriptor, as you'll see soon.

The rest of the setProperties() method configures the component based on the attributes that are unique for this custom action. For the DataBarTag, this means the first, rows, value, and var attributes. The page author may set the first two either to static values or to JSF EL expressions, so the tag handler needs to figure out which is which. It does so with the help of the isValueReference( ) method implemented by the UIComponentTag. This method returns true if the value is delimited by the #{ and } delimiters. If so, the setProperties() method converts the expression string into an instance of the javax.faces.el.ValueBinding class representing the value binding and calls the component's setValueBinding() method with the ValueBinding as an argument. The component evaluates the value binding as needed during the different request processing lifecycles, as described in the previous chapters. If the attribute value isn't a value binding expression, the setProperties() method casts the component reference to UIData and calls the corresponding type-safe property setter method.

The two remaining action attributes are handled slightly different. The value attribute must be set to a value binding expression, because the model can't be expressed as a static value in a meaningful way. The setProperties( ) method therefore assumes it's a value binding expression without checking the syntax first. The var attribute, on the other hand, must be a static value. This is a convention established by JSTL and JSP to make it possible to add more translation-time checking for variables in future versions of the specifications. The var attribute value is therefore always set as a static component property value.

As with any JSP tag handler class, it must also be declared in a TLD. I add the declaration to the same taglib.tld file as we created for the custom validation action in Chapter 7, like this:

<taglib>

  ...

  <tag>

    <name>dataBar</name>

    <tag-class>com.mycompany.jsf.taglib.DataBarTag</tag-class>

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



    <attribute>

      <name>id</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>binding</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>rendered</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>first</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>rows</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>value</name>

      <required>true</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>



    <attribute>

      <name>var</name>

      <required>false</required>

      <rtexprvalue>false</rtexprvalue>

    </attribute>

  </tag>

  ...

</taglib>

The tag handler is declared with the <tag> element, with its name and implementation class defined by the nested <name> and <tag-class> elements. The <body-content> element for a JSF component tag handler must be set to JSP to tell the JSP container to evaluate action elements in the body, e.g., action elements that represent child components, validators, or converters.

Each action attribute is described by an <attribute> element. The nested <name> element contains the attribute name and the <required> element tells if the attribute is required or not. The <rtexprvalue> element declares if the attribute can be set as a so-called request time attribute value, which is either a Java or a JSP EL expression. You must set this element value to false for JSF custom actions in order to prevent clashes and potential security issues caused by mixing a JSP EL or Java expression with a JSF EL expression. To the JSP container, a JSF EL expression is just a plain string, not a request-time attribute value; the tag handler is responsible for turning it into ValueBinding instances, as described earlier.

For the dataBar action, I declare the standard attributes implemented by the UIComponentTag class (id, binding, and rendered) plus the attributes added by the custom action tag handler.

13.1.1.4 Using the custom renderer

We now have a custom renderer for the UIData component and a JSP custom action for the combination. Example 13-1 shows the part of the modified reports list area JSP page that produces the screen in Figure 13-1 with the page links added by the bar renderer. The parts not shown are identical to the final version we looked at in Chapter 10.

Example 13-1. Navigation bar that is created by the Bar renderer (expense/stage4/reportListArea.jsp)
<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://mycompany.com/jsftaglib" prefix="my" %>



<html>

  <head>

    <title>Expense Reports</title>

    <link rel="stylesheet" type="text/css" 

      href="${pageContext.request.contextPath}/style.css">

  </head>

  <body>

    <f:view>

      <h:form>

        <h:dataTable value="#{reportHandler.sortedReportsModel}" var="report"

          rows="#{reportHandler.noOfRows}" 

          first="#{reportHandler.firstRowIndex}"

          styleClass="tablebg" rowClasses="oddRow, evenRow" 

          columnClasses="left, left, left, right, left">

          ...

        </h:dataTable>



        <h:commandButton value="<<" 

          disabled="#{reportHandler.scrollFirstDisabled}" 

          action="#{reportHandler.scrollFirst}" />

        <h:commandButton value="<" 

          disabled="#{reportHandler.scrollPreviousDisabled}" 

          action="#{reportHandler.scrollPrevious}" />

        <my:dataBar value="#{reportHandler.pages}" var="page" 

          rows="#{reportHandler.noOfPageLinks}" 

          first="#{reportHandler.firstPageIndex}">

          <h:column>

            <h:commandLink action="#{page.select}"

              immediate="true" 

              rendered="#{page.number != reportHandler.currentPage}">

              <h:outputText value="#{page.number}" style="padding: 1em" />

            </h:commandLink>

            <h:outputText value="#{page.number}" style="padding: 1em" 

              rendered="#{page.number == reportHandler.currentPage}" />

          </h:column>

        </my:dataBar>

        <h:commandButton value=">" 

          disabled="#{reportHandler.scrollNextDisabled}" 

          action="#{reportHandler.scrollNext}" />

        <h:commandButton value=">>" 

          disabled="#{reportHandler.scrollLastDisabled}" 

          action="#{reportHandler.scrollLast}" />

        Rows/page: 

        <h:inputText value="#{reportHandler.noOfRows}" size="3"/>

        <h:commandButton value="Refresh" />

      </h:form>

    </f:view>

  </body>

</html>

I've added the taglib directive for the custom tag library holding the custom action and inserted the <my:dataBar> custom action between the command buttons for scrolling.

In order to use the <my:dataBar> custom action, we need to provide the UIData component it represents with a model with one row per page of expense reports and properties for the first and rows attributes. We also need an action method that selects a specific page and adjusts the row index for the model used by the <h:dataTable> action in the same page that displays the expense reports table. Because of the tight interaction between the two models used in this page, I've added all these things to the ReportHandler class we used in the previous chapters to control the expense reports list.

The <my:dataBar> attributes values bind the component to the new ReportHandler properties. The value attribute is bound to a getter method that returns a model suitable for the page navigation bar component, implemented like this:

package com.mycompany.expense;



import java.util.ArrayList;

import java.util.List;

...

public class ReportHandler {



    private int noOfRows = 5;

    private int firstRowIndex = 0;

    private int noOfPageLinks = 5;

    ...



    public List getPages( ) {

        int totalNoOfRows = getSortedReportsModel( ).getRowCount( );

        int noOfPages =  totalNoOfRows / noOfRows;

        if (totalNoOfRows % noOfRows > 0) {

            noOfPages += 1;

        }



        List pages = new ArrayList(noOfPages);

        for (int i = 0; i < noOfPages; i++) {

            pages.add(new Page(i + 1, this));

        }

        return pages;

    }

The getPages() method first finds out how many rows there are in the reports table model and divides that number with the number of rows shown per page to get the total number of pages. If it doesn't divide evenly, it adds one to the number of pages to account for the incomplete last page. Next, getPages() creates a List with a Page instance per page and returns it. The Page class is an inner class that looks like this:

public static class Page {

    private int number;

    private ReportHandler handler;



    public Page(int number, ReportHandler handler) {

        this.number = number;

        this.handler = handler;

    }



    public int getNumber( ) {

        return number;

    }



    public String select( ) {

        handler.setCurrentPage(number); 

        return null;

    }

}

It holds the number for the page it represents and a reference to the ReportHandler class it belongs to. A getNumber() method returns the page number and a select() method tells the ReportHandler that the page has been selected. We'll look at how the select() method is used shortly.

The rows attribute tells how many rows to process per page. When the UIData component is used with the bar renderer to generate page navigation links, rows is the number of page links to render at the most. The attribute is bound to a property named noOfPageLinks, implemented like this in the ReportHandler class:

public int getNoOfPageLinks( ) {

    return noOfPageLinks;

}

The property getter method just returns the value of the corresponding instance variable. Nothing prevents you from adding a setter method and binding it to an input component so the user can change the value, but the number of links to render is most likely fixed. The only reason for defining this value as a property instead of setting it as a static attribute value in the JSP page is that I need the value in the bean to calculate other information later.

The first attribute is bound to a property named firstPageIndex. As the name implies, it tells the component which row in the model to start at for each page. The property getter method looks like this:

public int getFirstPageIndex( ) {

    int noOfPages = getPages( ).size( );

    if (noOfPages <= noOfPageLinks) {

        return 0;

    }



    int firstPageIndex = (firstRowIndex / noOfRows) - 1;

    if (firstPageIndex < 0) {

        firstPageIndex = 0;

    }

    else if (noOfPages - firstPageIndex < noOfPageLinks) {

        firstPageIndex = noOfPages - noOfPageLinks;

    }

    return firstPageIndex;

}

This one is a bit more complex because it must ensure that the rendered page links are synchronized with the reports rendered in the reports table. The getFirstPageIndex() method first figures out how many pages there are by checking the size of the List returned by the getPages( ) method. If the number of pages is less than the maximum number of links to render, getFirstPageIndex() returns 0 so that links for all pages are rendered. If there are more pages than links that should be rendered, the index is set to the index of the first report that's currently rendered divided by the number of reports displayed per page, minus one. This value is then adjusted to 0 if it happens to result in a negative value, or to an index that ensures that the maximum number of links can be rendered even if the current page is one of the last few pages. This algorithm ensures that the maximum number of links is always rendered and there's always a link to the previous page, if any. If you find it hard to visualize, experiment with the sample page shown in Example 13-1.

The body of the <my:dataBar> action element contains one <h:column> action element, representing the single column component child element. This element in turn contains two JSF action elements for rendering a page reference per row in the model. The first action element is <h:commandLink> with an action attribute that binds it to a method named select() in the bean exposed through the variable named by the <my:dataBar> var attribute. The model for the <my:dataBar> action is a List of Page instances, so when the user clicks the link, the select() method in the Page instance represented by the link is invoked. As you saw earlier, the Page select() method calls the setCurrentPage() on the ReportHandler with its own page number as the argument. The setCurrentPage() method is implemented like this:

private void setCurrentPage(int currentPage) {

    firstRowIndex = (currentPage - 1) * noOfRows;

}

It sets the instance variable that holds the index for the first row in the reports table to the provided page number minus one and multiplied by the number of rows displayed per page, resulting in the index for the top row of the selected page. The <h:dataTable> for the reports table is bound to the firstRowIndex property, so when the page is rendered, the reports on the selected page are rendered.

The rendered attribute for the <h:commandLink> element contains a JSF EL expression that compares the page number represented by the current Page instance with the value of the currentPage property of the ReportHandler. The expression evaluates to true if they are different, so the link is rendered for all pages except the current one. The currentPage property getter method looks like this:

public int getCurrentPage( ) {

    return (firstRowIndex / noOfRows) + 1;

}

It calculates the number for the current page by dividing the index for the current first row in the reports table with the number of reports displayed per page plus one.

The second column action child is an output component that is represented by an <h:outputText> action element. This element has a JSF expression as the rendered attribute value that evaluates to the opposite result: true if the current page is the same as that represented by the Page instance. The effect is that the value for the current page is rendered as text instead of as a link.

For both the <h:commandLink> element and the <h:outputText> element, a style attribute defines padding for the text they generate so that the page numbers are separated by a bit of space in the browser.

13.1.2 A Renderer for Encoding and Decoding

To customize the way the value for an input component is entered, we must develop a custom renderer that implements the decoding behavior in addition to the encoding behavior. An example is a custom renderer for the UIInput component that lets the user pick a date by selecting the year, month, and day from three selection lists, as shown in Figure 13-2.

Figure 13-2. Date selection lists rendered by a custom renderer for an input component
figs/Jsf_1302.gif

Because the date value is rendered as three selection lists, it's sent to the server as three separate parameters. The custom renderer reads all three and creates a single java.util.Date value that the input component can handle.

Before we dig into the code, let's see how the custom renderer can be used. Example 13-2 shows the JSP page using the custom action element that ties together the custom renderer and the input component.

Example 13-2. Using the date picker (custom/datePicker.jsp)
<%@ page contentType="text/html" %>

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

<%@ taglib uri="http://mycompany.com/jsftaglib" prefix="my" %>



<jsp:useBean id="now" scope="request" class="java.util.Date" />

<f:view>

  <html>

    <body>

      <h:form>

        <my:inputDatePicker value="#{myDate}"

          startYear="#{now.year + 1900 - 2}" years="10" />

        <h:outputText value="#{myDate}" />

        <br>

        <h:commandButton value="Submit" />

      </h:form>

    </body>

  </html>

</f:view>

As for the bar renderer described in the previous section, the date-picker renderer is backed by a custom action in the sample tag library, so the JSP page contains a taglib directive for this library.

The <my:inputDatePicker> custom action creates an input component and configures it to use the custom date-picker renderer we'll develop in this section. The action supports four attributes: value, startYear, years, and styleClass. The tag handler uses the value attribute to set the input component's value property. In Example 13-2, I bind it to a simple name, which JSF evaluates to a request scope variable. You can of course bind it to a bean property, in the same way as for all other components. I use a request scope variable here just to show you that it works and to keep the example simple.

The other attributes correspond to render-dependent attributes supported by the custom renderer. The startYear attribute value is the first year that should be displayed in the year selection list. In Example 13-2, I set it to two years prior to the current year with the help of a java.util.Date instance created by the <jsp:useBean> standard action. The year property of a Date is the year minus 1900, so I first add 1900 and then subtract two from this value. The years attribute is for the number of years to show in the year selection list. The final supported attribute is styleClass. I don't use it in this example, but it can be used to assign a CSS class name to the generated HTML element, just as for all the standard JSF action elements.

All attributes accept JSF EL expressions and they are all optional. If value isn't set or evaluates to null, the current date is used. The default for startYear is the current year and the default for years is five years.

Just as we did for the bar renderer described in the previous section, we must develop the custom renderer class, register the renderer class with JSF, and implement a JSP custom action that represents a UIInput component equipped with the custom renderer.

13.1.2.1 The Renderer class

The custom renderer is implemented as a class called com.mycompany.jsf.renderer.DatePickerRenderer. Compared to the bar renderer from the previous section, the primary difference in this renderer is that it implements decoding and conversion behavior in addition to its encoding behavior. The decoding and conversion logic is implemented like this:

package com.mycompany.jsf.renderer;



import java.io.IOException;

import java.text.DateFormatSymbols;

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.Iterator;

import java.util.List;

import java.util.Map;



import javax.faces.context.FacesContext;

import javax.faces.context.ResponseWriter;

import javax.faces.component.EditableValueHolder;

import javax.faces.component.ValueHolder;

import javax.faces.component.UIComponent;

import javax.faces.model.SelectItem;

import javax.faces.render.Renderer;



public class DatePickerRenderer extends Renderer {



    public void decode(FacesContext context, UIComponent component) {



        if (isDisabledOrReadOnly(component)) {

            return;

        } 

        

        String clientId = component.getClientId(context);

        Map params = context.getExternalContext( ).getRequestParameterMap( );

        String year = (String) params.get(clientId + "_year");

        if (year != null) {

            Map dateParts = new HashMap( );

            dateParts.put("year", year);

            dateParts.put("month", params.get(clientId + "_month"));

            dateParts.put("day", params.get(clientId + "_day"));

            ((EditableValueHolder) component).setSubmittedValue(dateParts);

        }

    }

JSF calls the decode() method of each UIComponent instance with a rendered property set to true during the Apply Request Values phase, and a component configured with a renderer delegates the call to its renderer. The UIForm component modifies this behavior so that the decode() method is invoked on its children only if the form is submitted. Hence, if you have more than one form in the same view, only the components in the submitted form are decoded.

The decode() method should always check if the component is marked as disabled or read-only before proceeding, to prevent the component's value from being set in these cases. If a component is rendered as disabled, the browser doesn't send a parameter for the corresponding field; unless the decoding processes is aborted, the normal decoding logic sets the value to null. The browser may send a value for a read-only field, but the component still shouldn't be decoded because a component marked as read-only shouldn't be updated. To keep this example simple, the DatePickerRenderer doesn't support the readonly and disabled attributes, but I included this piece of logic in the decode() method anyway, because it's an important requirement for renderers that do support these attributes and it isn't obvious that the test is needed. The isDisabledOrReadOnly() method returns true if one or both of these attributes are enabled:

private boolean isDisabledOrReadOnly(UIComponent component) {

    boolean disabled = false;

    boolean readOnly = false;



    Object disabledAttr = component.getAttributes( ).get("disabled");

    if (disabledAttr != null) {

        disabled = disabledAttr.equals(Boolean.TRUE);

    }

    Object readOnlyAttr = component.getAttributes( ).get("readonly");

    if (readOnlyAttr != null) {

        readOnly = readOnlyAttr.equals(Boolean.TRUE);

    }

    return disabled || readOnly;

}

Both attributes are render-dependent, so they are included in the component's list of generic attributes accessible through the getAttributes() method if they are set. The isDisabledOrReadOnly() method tries to get the attributes from the list and returns true if one or both are set to Boolean.TRUE.

If it's okay to proceed, the decode( ) method gets a Map containing all request parameters from the ExternalContext and looks for the parameter that holds the year value. The parameter name for the year is the component's client ID plus the string _year. If it finds this parameter, it creates a Map and populates it with the year plus the month and day values from similarly named parameters. It saves the Map as the submittedValue property of the component after casting the component reference to the EditableValueHolder type. This is an interface that all input components implement, containing the methods related to the component's editable value, such as the submittedValue property setter method.

When all of the components in the component tree have been decoded, the request processing lifecycle enters the Process Validations phase, as you may recall from Chapter 8. The first thing that takes place in this phase is the conversion of the submitted value into the data type that the component needs. Because only the renderer knows the form in which the submitted value is saved, the component asks its renderer to perform the conversion by calling the getConvertedValue() method:

public Object getConvertedValue(FacesContext context, 

    UIComponent component, Object submittedValue) {



    Map dateParts = (Map) submittedValue;

    int year = Integer.parseInt((String) dateParts.get("year"));

    int month = Integer.parseInt((String) dateParts.get("month"));

    int day = Integer.parseInt((String) dateParts.get("day"));

    return new Date(year - 1900, month - 1, day);

}

For the DatePickerRenderer, this method extracts the year, month, and day values from the Map passed as the submitted value and converts them into a Date instance that it returns. The component saves the returned value as its local value and resets the submitted value.

In other renderers, this method may instead get a Converter instance from the component by casting the component reference to ValueHolder and calling its getConverter() method, or locate a Converter for the data type of the property the component is bound to, and use the Converter to convert the value as discussed in Chapter 7. In this case, the conversion may fail, so the getConvertedValue() method may throw a ConverterException. If that happens, the component keeps its submitted value so it can be displayed when the component is rendered again along with an error message.

All rendering logic for the DatePickerRenderer is implemented in the encodeBegin() method:

public void encodeBegin(FacesContext context, UIComponent component) 

        throws IOException {



    if (!component.isRendered( )) {

        return;

    }



    Date date = null;

    Map submittedValue = (Map)

        ((EditableValueHolder) component).getSubmittedValue( );

    if (submittedValue != null) {

        date = (Date) getConvertedValue(context, component, submittedValue);

    }

    else {

        Object value = ((ValueHolder) component).getValue( );

        date = value instanceof Date ? (Date) value : new Date( );

    }



    String styleClass =

        (String) component.getAttributes( ).get("styleClass");

    int startYear = date.getYear( ) + 1900;

    Object startYearAttr = component.getAttributes( ).get("startYear");

    if (startYearAttr != null) {

        startYear = ((Number) startYearAttr).intValue( );

    }

    int years = 5;

    Object yearsAttr = component.getAttributes( ).get("years");

    if (yearsAttr != null) {

        years = ((Number) yearsAttr).intValue( );

    }



    String clientId = component.getClientId(context);

    ResponseWriter out = context.getResponseWriter( );

    renderMenu(out, getYears(startYear, years), date.getYear( ) + 1900, 

        clientId + "_year", styleClass, component);

    renderMenu(out, getMonths( ), date.getMonth( ) + 1, 

        clientId + "_month", styleClass, component);

    renderMenu(out, getDays( ), date.getDate( ),

        clientId + "_day", styleClass, component);

}

The encodeBegin() method returns immediately if the component's rendered property is set to false, just as for the BarRenderer we looked at earlier.

A renderer for an input component must render the submitted value if it's set, and use the local value only if there's no submitted value. This rule handles the case where the user has entered a value that doesn't pass conversion or validation rules. If so, the component's submitted value remains; otherwise, it's reset at the same time as the validated value is saved as the local value. The encodeBegin() method first tries to get the submitted value and converts it to a java.util.Date by calling the getConvertedValue() method if it's set. If no submitted value is available, the local value is used as is or a new Date instance is created as a default value. The encodeBegin() method also obtains all supported render-dependent attribute values, or establishes defaults for the attributes that aren't set.

Next, it gets the client ID for the component. Because this is an input component, the client ID must be used as an attribute of the generated HTML element—whether it's set explicitly by the page author—in order for the renderer to figure out which parameter holds the component value when the form it belongs to is submitted.

It then renders the three selection lists by calling the private renderMenu() method for each list:

private void renderMenu(ResponseWriter out, List items, int selected, 

    String clientId, String styleClass, UIComponent component) 

    throws IOException {



    out.startElement("select", component);

    out.writeAttribute("name", clientId, "id");

    if (styleClass != null) {

        out.writeAttribute("class", styleClass, "styleClass");

    }

        

    Iterator i = items.iterator( );

    while (i.hasNext( )) {

        SelectItem si = (SelectItem) i.next( );

        Integer value = (Integer) si.getValue( );

        String label = si.getLabel( );

        out.startElement("option", component);

        out.writeAttribute("value", value, null);

        if (value.intValue( ) == selected) {

            out.writeAttribute("selected", "selected", null);

        }

        out.writeText(label, null);

    }

    out.endElement("select");

}

The renderMenu() method is called with references to the ResponseWriter, a List of SelectItem instances for the selection list, the value for the choice that should be selected, the client ID, a CSS class, and the component. The method renders a <select> element with the client ID as the name attribute and a class attribute with the CSS class if one is specified.

The method then iterates through the List of SelectItem instances and generates an <option> element for each, adding the selected attribute for the element represented by a SelectItem with a value that matches the selected method argument.

Three private methods provide a List with SelectItem instances for each selection list:

private List getYears(int startYear, int noOfyears) {

    List years = new ArrayList( );

    for (int i = startYear; i < startYear + noOfyears; i++) {

        Integer year = new Integer(i);

        years.add(new SelectItem(year, year.toString( )));

    }

    return years;

}



private List getMonths( ) {

    DateFormatSymbols dfs = new DateFormatSymbols( );

    String[] names = dfs.getMonths( );

    List months = new ArrayList( );

    for (int i = 0; i < 12; i++) {

        Integer key = new Integer(i + 1);

        String label = names[i];

        months.add(new SelectItem(key, label));

    }

    return months;

}



private List getDays( ) {

    List days = new ArrayList( );

    for (int i = 1; i < 32; i++) {

        Integer day = new Integer(i);

        days.add(new SelectItem(day, day.toString( )));

    }

    return days;

}

The getYears() and getDays() methods simply create SelectItem instances with numeric key and label values. The getMonths() method uses the java.text.DateFormatSymbols class to get the month names and uses them as labels.

13.1.2.2 Registering the renderer

As before, we must register the date-picker renderer in the faces-config.xml file:

<faces-config>

    ...

    <renderer>

      <component-family>javax.faces.Input</component-family>

      <renderer-type>com.mycompany.DatePicker</renderer-type>

      <renderer-class>

        com.mycompany.jsf.renderer.DatePickerRenderer

      </renderer-class>

    </renderer>

    ...

</faces-config>

This is a renderer for a UIInput component, so we set the <component-family> element value to javax.faces.Input. For the <renderer-type> element, we use the value com.mycompany.DatePicker, following the same convention as for the bar custom renderer.

13.1.2.3 The JSP tag handler class

The JSP tag handler class for the <my:inputDatePicker> action follows the same pattern as the tag handler for the <my:dataBar> action:

package com.mycompany.jsf.taglib;



import javax.faces.webapp.UIComponentTag;

import javax.faces.component.UIComponent;

import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;



public class InputDatePickerTag extends UIComponentTag {



    private String startYear;

    private String years;

    private String value;

    private String styleClass;



    public void setStartYear(String startYear) {

        this.startYear = startYear;

    }



    public void setYears(String years) {

        this.years = years;

    }



    public void setValue(String value) {

        this.value = value;

    }



    public void setStyleClass(String styleClass) {

        this.styleClass = styleClass;

    }



    public String getComponentType( ) {

        return "javax.faces.Input";

    }



    public String getRendererType( ) {

        return "com.mycompany.DatePicker";

    }



    protected void setProperties(UIComponent component) {

        super.setProperties(component);



        FacesContext context = getFacesContext( );

        if (startYear != null) {

            if (isValueReference(startYear)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(startYear);

                component.setValueBinding("startYear", vb);

            } else {

                component.getAttributes( ).put("startYear", 

                    new Integer(startYear));

            }

        }



        if (years != null) {

            if (isValueReference(years)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(years);

                component.setValueBinding("years", vb);

            } else {

                component.getAttributes( ).put("years", new Integer(years));

            }

        }



        if (value != null) {

            if (isValueReference(value)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(value);

                component.setValueBinding("value", vb);

            } else {

                component.getAttributes( ).put("value", value);

            }

        }

                

        if (styleClass != null) {

            if (isValueReference(styleClass)) {

                ValueBinding vb = 

                    context.getApplication( ).createValueBinding(styleClass);

                component.setValueBinding("styleClass", vb);

            } else {

                component.getAttributes( ).put("styleClass", styleClass);

            }

        }

    }

}

The InputDatePickerTag class extends the UIComponentTag and provides setter methods for all the supported attributes: value, startYear, years, and styleClass.

The getComponentType() method returns javax.faces.Input and the getRendererType() method returns com.mycompany.DatePicker. The UIComponentTag uses these values to create an input component and configure it to use the custom renderer. The setProperties() method sets the component properties and generic attributes corresponding to the custom action attributes, just as for the DataBarTag class described in the previous section.

In order to focus on the most important aspects of a renderer for an input component, I've left out a number of attributes that you would most likely want to support in a production version of this type of custom action and custom renderer. For instance, the JSF <h:inputText> action supports the immediate and valueChangeListener attributes. To support them for the date picker custom action, just add tag handler setter methods and code in the setProperties() method for setting the values on the component, either as generic attributes or by casting the component reference to EditableValueHolder and calling the type-safe setter methods.

Similarly, the <h:inputText> action supports a number of render-dependent attributes in addition to the styleClass attribute, such as style, disabled, readonly, onchange, and so on. To add support for render-dependent attributes like this, add a setter method in the tag handler class and code for setting the attributes as generic attributes in the setProperties( ) method, plus the same kind of code that the DatePickerRenderer uses to render the styleClass attribute value for all the additional attributes.

    Previous Section  < Day Day Up >  Next Section