Download
FAQ
History
PrevHomeNext 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. The state information includes the component's type, identifier, and local value. The behavior defined by the component class includes the following:

The UIComponentBase class defines the default behavior of a component class. All 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 must 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.

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, 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 to make the menu editable.

Whether you decide to have your component extend UIComponentBase or a standard component, you might also want your component to implement one or more of these behavioral interfaces:

If your component extends UICommand, it automatically implements ActionSource and StateHolder. If your component extends UIOutput or one of the component classes that extend UIOutput, it automatically implements StateHolder and ValueHolder. If your component extends UIInput, it automatically implements EditableValueHolder, StateHolder, and ValueHolder. If your component extends UIComponentBase, it automatically implements only StateHolder. See the JavaServer Faces API Javadoc to find out what the other component classes implement.

So if you create a component that extends UIInput, for example, and if you want this component to fire action events, your component also must implement ActionSource.

The image map example has two component classes: AreaComponent and MapComponent. The MapComponent class extends UICommand. The AreaComponent class extends the standard component UIOutput.

The MapComponent class represents the component corresponding to the map tag:

<bookstore:map id="worldMap" current="NAmericas" 
immediate="true" 
  action="storeFront"
  actionListener="#{localeBean.chooseLocaleFromMap}"> 

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

<bookstore:area id="NAmerica" value="#{NA}"
  onmouseover="/template/world_namer.jpg" 
  onmouseout="/template/world.jpg" 
  targetImage="mapImage" />  

MapComponent has one or more AreaComponents as children. Its behavior consists of the following

The rendering of the map and input tags is actually performed by the bookstore6/src/renderers/MapRenderer, but MapComponent delegates this rendering to MapRenderer.

Because MapComponent extends UICommand, it automatically implements ActionSource so that it can fire an ActionEvent when a user clicks on the map.

The AreaComponent class extends UIOutput because AreaComponent requires a value attribute, which is already defined by UIOutput.

AreaComponent is bound to a bean that stores the shape and coordinates of the region of the image map. You'll see how all this data is accessed through the value expression in Create the Renderer Class. The behavior of the AreaComponent component consists of the following

Although these tasks are actually performed by bookstore6/src/renderers/AreaRenderer, the AreaComponent class must delegate the tasks to AreaRenderer. See Delegating Rendering to a Renderer for more information.

The rest of this section details how the MapRenderer class performs encoding and decoding, how it defines properties for the component's local values, and how it saves the state of MapComponent. Handling Events for Custom Components details how MapComponent handles events.

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, and 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.

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

The JavaServer Faces implementation automatically invokes the encodeEnd method of AreaComponent's renderer after it invokes MapRenderer's encodeBegin method and before it invokes MapRenderer'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 MapRenderer:

public void encodeBegin(FacesContext context, 
  UIComponent component) throws IOException {
  if ((context == null)|| (component == null)){
    throw new NullPointerException();
  }
  MapComponent map=(MapComponent) component;
  ResponseWriter writer = context.getResponseWriter();
  writer.startElement("map", map);
  writer.writeAttribute("name", map.getId(),"id");
} 
public void encodeEnd(FacesContext context) throws IOException 
{
  if ((context == null) || (component == null)){
    throw new NullPointerException();
  }
  MapComponent map = (MapComponent) component;
  ResponseWriter writer = context.getResponseWriter();
  writer.startElement("input", map);
  writer.writeAttribute("type", "hidden", null);
  writer.writeAttribute("name", 
    getName(context,map), "clientId");(
  writer.endElement("input");
  writer.endElement("map");
} 

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

The encoding methods accept a UIComponent argument and a FacesContext argument. The FacesContext contains all the information associated with the current request. The UIComponent argument is the component that needs to be rendered. The renderer must be told what component it is rendering. So you must pass the component to the encoding methods of the renderer.

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

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

After calling startElement, you can call writeAttribute to render the tag's attributes. The writeAttribute method takes the name of the attribute, its value, and the name of a property or attribute of the containing component corresponding to the attribute. The last parameter can be null, and it won't be rendered.

The name attribute value of the map tag is retrieved using the getId method of UIComponent, which returns the component's unique identifier. The name attribute value of the input tag is retrieved using the getName(FacesContext, UIComponent) method of MapRenderer.

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 encoding method to check whether 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's encodeEnd method, which does the work of finding the renderer. The MapComponent class delegates all rendering to MapRenderer, so it does not need to check for available renderers.

In some custom component classes that extend standard components, you might need to implement other methods in addition to encodeEnd. For example, if you need to retrieve the component's value from the request parameters--to, for example, update a bean's values--you must also 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 that is acceptable to the component class.

A custom component class or its renderer must implement the decode method only if it must retrieve the local value or if it needs to queue events. The MapRenderer retrieves the local value of the hidden input field and sets the current attribute to this value by using its decode method. The setCurrent method of MapComponent queues the event by calling queueEvent, passing in the AreaSelectedEvent generated by MapComponent.

Here is the decode method of MapRenderer:

public void decode(FacesContext context, UIComponent component) 
{
  if ((context == null) || (component == null)) {
    throw new NullPointerException();
  }
  MapComponent map = (MapComponent) component;
  String key = getName(context, map);
  String value = (String)context.getExternalContext().
    getRequestParameterMap().get(key);
  if (value != null) 
    map.setCurrent(value);
  }
} 

The decode method first gets the name of the hidden input field by calling getName(FacesContext, UIComponent). It then uses that name as the key to the request parameter map to retrieve the current value of the input field. This value represents the currently selected area. Finally, it sets the value of the MapComponent class's current attribute to the value of the input field.

Enabling Value-Binding of Component Properties

Creating the Component Tag Handler describes how MapTag sets the component's values when processing the tag. For those component attributes that take value-binding expressions that point to a backing bean property, MapTag uses a ValueBinding to evaluate the expression.

To get the value of a component attribute that accepts a value-binding expression pointing to a backing bean property, the component class must get the ValueBinding associated with the attribute. Because MapComponent extends UICommand, the UICommand already does the work of getting the ValueBinding associated with each of the attributes that it supports. However, if you have a custom component that extends UIComponentBase, you will need to get the ValueBinding associated with those attributes that are value-binding enabled. For example, if MapComponent extended UIComponentBase instead of UICommand, it would need to include a method that gets the ValueBinding for the immediate attribute:

public boolean isImmediate() {
  if (this.immediateSet) {
      return (this.immediate);
  }
  ValueBinding vb = getValueBinding("immediate");
  if (vb != null) {
    Boolean value = (Boolean) vb.getValue(getFacesContext());
    return (value.booleanValue());
  } else {
      return (this.immediate);
  }
}  

The properties corresponding to the component attribute that accepts a method-binding expression pointing to a backing bean method must accept and return a MethodBinding. For example, if MapComponent extended UIComponentBase instead of UICommand, it would need to provide an action property that returns and accepts a MethodBinding:

public MethodBinding getAction() {
  return (this.action);
}
public void setAction(MethodBinding action) {
  this.action = action;
}
   

Saving and Restoring State

Because component classes implement StateHolder, they must implement the saveState(FacesContext) and restoreState(FacesContext, Object) methods to help the JavaServer Faces implementation save and restore the state of your component across multiple requests.

To save a set of values, you must implement the saveState(FacesContext) method. This method is called during the render response phase, during which the state of the response is saved for processing on subsequent requests. Here is the method from MapComponent:

public Object saveState(FacesContext context) {
  Object values[] = new Object[2];
  values[0] = super.saveState(context);
  values[1] = current;
  return (values);
} 

This method initializes an array, which will hold the saved state. It next saves all of the state associated with MapComponent.

A component that implements StateHolder must also provide an implementation for restoreState(FacesContext, Object), which restores the state of the component to that saved with the saveState(FacesContext) method. The restoreState(FacesContext, Object) is called during the restore view phase, during which the JavaServer Faces implementation checks whether there is any state that was saved during the last render response phase and needs to be restored in preparation for the next postback. Here is the restoreState(FacesContext, Object) method from MapComponent:

public void restoreState(FacesContext context, Object state) {
  Object values[] = (Object[]) state; 
  super.restoreState(context, values[0]);
  current = (String) values[1];
} 

This method takes the FacesContext and the Object, representing the array that is holding the state for the component. This method sets the component's properties to the values saved in the Object array.

When you implement these methods in your component class, be sure to specify in your web.xml file where you want the state to be saved: either client or server. Here is the context-param element from the components demo web.xml file. It specifies that state must be saved in the client:

<context-param>
  <param-name>javax.faces.STATE_SAVING_METHOD</param-name> 
  <param-value>client</param-value>
</context-param> 

If state is saved on the client, the state of the entire view is rendered to a hidden field on the page.

Divider
Download
FAQ
History
PrevHomeNext API
Search
Feedback
Divider

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