Download
FAQ
History
HomeHomeNext API
Search
Feedback
Divider

Creating Custom Component Classes

As explained in When to Use a Custom Component, a component class defines the state and behavior of a UI component. Some of the state information includes the component's type, identifier, and local value. Some of the behavior defined by the component class includes:

The UIComponentBase class defines the default behavior of a component class. All of the classes representing the standard components extend from UIComponentBase. These classes add their own behavior definitions, as your custom component class will do.

Your custom component class needs to either extend UIComponentBase directly or extend a class representing one of the standard components. These classes are located in the javax.faces.component package and their names begin with UI.

To decide whether you need to extend directly from UIComponentBase or from one of the standard component classes, consider what behavior you want your component to have. If one of the standard component classes defines most of the functionality you need, you should extend that class rather than UIComponentBase. For example, suppose you want to create an editable menu component. It makes sense to have this component extend UISelectOne rather than UIComponentBase because you can reuse the behavior already defined in UISelectOne. The only new functionality you need to define is that which makes the menu editable.

The image map example has two component classes: UIArea and UIMap. The UIMap component class extends the standard component, UICommand. The UIArea class extends the standard component, UIOutput.

This following sections explain how to extend a standard component and how to implement the behavior for a component.

Extending From a Standard Component

Both UIMap and UIArea extend from standard components. The UIMap class represents the component corresponding to the map tag:

<d:map id="worldMap" currentArea="NAmericas" />  

The UIArea class represents the component corresponding to the area tag:

  <d:area id="NAmericas" valueRef="NA"
    onmouseover="/world_namer.jpg" onmouseout="/world.jpg" /> 

The UIMap component has one or more UIArea components as children. Its behavior consists of:

The UIMap class extends from UICommand because UIMap generates an ActionEvent when a user clicks on the map. Since UICommand components already have the ability to generate this kind of event, it makes sense to extend UICommand rather than redefining this functionality in a custom component extending from UIComponentBase.

The UIArea component class extends UIOutput because UIArea requires a value and valueRef attribute, which are already defined by UIOutput.

The UIArea component is bound to a model object that stores the shape and coordinates of the region of the image map. You'll see how all of this data is accessed through the valueRef expression in Performing Encoding. The behavior of the UIArea component consists of:

Although these tasks are actually performed by AreaRenderer, the UIArea component class must delegate the tasks to AreaRenderer. See Delegating Rendering to a Renderer for more information.

The rest of these components' behavior is performed in its encoding and decoding methods. Performing Encoding and Performing Decoding explain how this behavior is implemented.

Performing Encoding

During the Render Response phase, the JavaServer Faces implementation processes the encoding methods of all components and their associated renderers in the tree. The encoding methods convert the current local value of the component into the corresponding markup that represents it in the response.

The UIComponentBase class defines a set of methods for rendering markup: encodeBegin, encodeChildren, encodeEnd. If the component has child components, you might need to use more than one of these methods to render the component; otherwise, all rendering should be done in encodeEnd.

Since UIMap is a parent component of UIArea, the area tags must be rendered after the beginning map tag and before the ending map tag. To accomplish this, the UIMap class renders the beginning map tag in encodeBegin and the rest of the map tag in encodeEnd.

The JavaServer Faces implementation will automatically invoke the encodeEnd method of the UIArea component's renderer after it invokes UIMap's encodeBegin method and before it invokes UIMap's encodeEnd method. If a component needs to perform the rendering for its children, it does this in the encodeChildren method.

Here are the encodeBegin and encodeEnd methods of UIMap:

public void encodeBegin(FacesContext context) throws 
IOException {
  if (context == null) {
    System.out.println("Map: context is null");
    throw new NullPointerException();
  }
  ResponseWriter writer = context.getResponseWriter();
  writer.write("<Map name=\"");
  writer.write(getComponentId());
  writer.write("\">");
} 
public void encodeEnd(FacesContext context) throws IOException 
{
  if (context == null) {
    throw new NullPointerException();
  }
  ResponseWriter writer = context.getResponseWriter();
  writer.write(
    "<input type=\"hidden\" name=\"selectedArea\"");
  writer.write("\">");
  writer.write("</Map>");
} 

Notice that encodeBegin renders only the beginning map tag. The encodeEnd method renders the input tag and the ending map tag.

These methods first check if the FacesContext is null. The FacesContext contains all of the information associated with the current request.

You also need a ResponseWriter, which you get from the FacesContext. The ResponseWriter writes out the markup to the current response.

The rest of the method renders the markup to the ResponseWriter. This basically involves passing the HTML tags and attributes to the ResponseWriter as strings, retrieving the values of the component attributes, and passing these values to the ResponseWriter.

The id attribute value is retrieved with the getComponentId method, which returns the component's unique identifier. The other attribute values are retrieved with the getAttribute method, which takes the name of the attribute.

If you want your component to perform its own rendering but delegate to a Renderer if there is one, include the following lines in the encode method to check if there is a renderer associated with this component.

  if (getRendererType() != null) {
    super.encodeEnd(context);
    return;
  } 

If there is a Renderer available, this method invokes the superclass' encodeEnd method, which does the work of finding the renderer. The UIMap class performs its own rendering so does not need to check for available renderers.

In some custom component classes that extend standard components, you might need to implement additional methods besides encodeEnd. For example, if you need to retrieve the component's value from the request parameters--such as to update a model object--you also have to implement the decode method.

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 acceptable to the component class.

A custom component class needs to implement the decode method only if it must retrieve the local value, or it needs to queue events onto the FacesContext. The UIMap component must do both of the tasks. Here is the decode method of UIMap:

public void decode(FacesContext context) throws IOException {
  if (context == null) {
    throw new NullPointerException();
  }
  String value =
    context.getServletRequest().getParameter("selectedArea");
  if (value != null) 
    setAttribute("currentArea", value);
    context.addFacesEvent(
      new ActionEvent(this, commandName));
    setValid(true);
} 

The decode method first extracts the value of selectedArea from the request parameters. Then, it sets the value of UIMap's currentArea attribute to the value of selectedArea. The currentArea attribute value indicates the currently-selected area.

The decode method queues an action event onto the FacesContext. In the JSP page, the action_listener tag nested inside the map tag causes the ImageMapEventHandler to be registered on the map component. This event handler will handle the queued event during the Apply Request Values phase, as explained in Handling Events for Custom Components.

Finally, the decode method calls setValid(true) to confirm that the local values are valid.

Divider
Download
FAQ
History
HomeHomeNext API
Search
Feedback
Divider

All of the material in The Java(TM) Web Services Tutorial is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.