Download PDF Home History |  | API Search Feedback FAQ

> ## 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:

> - Decoding (converting the request parameter to the component's local value)
> - Encoding (converting the local value into the corresponding markup)
> - Saving the state of the component
> - Updating the bean value with the local value
> - Processing validation on the local value
> - Queueing events

> 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:

> - `ActionSource`: Indicates that the component can fire an `ActionEvent`
> - `EditableValueHolder`: Extends `ValueHolder` and specifies additional features for editable components, such as validation and emitting value-change events
> - `NamingContainer`: Mandates that each component rooted at this component have a unique ID
> - `StateHolder`: Denotes that a component has state that must be saved between requests
> - `ValueHolder`: Indicates that the component maintains a local value as well as the option of accessing data in the model tier

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

> If you want your custom component to exhibit the behavior of one of these interfaces, it must either explicitly implement the interface or extend a standard component class that implements the interface. For example, if you have a component that extends `UIInput` and you want it to fire action events, it must also implement `ActionSource`.

> The image map example has two component classes: `AreaComponent` and `MapComponent`. The `MapComponent` class extends `UICommand` and therefore implements `ActionSource`, which means it can fire action events when a user clicks on the map. 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="bookstore"
>       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 `AreaComponent` instances as children. Its behavior consists of the following

> - Retrieving the value of the currently selected area
> - Defining the properties corresponding to the component's values
> - Generating an event when the user clicks on the image map
> - Queuing the event
> - Saving its state
> - Rendering the `map` tag and the `input` tag

> The rendering of the `map` and `input` tags is performed by `MapRenderer`, but `MapComponent` delegates this rendering to `MapRenderer`.

> `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 Creating the Renderer Class. The behavior of `AreaComponent` consists of the following

> - Retrieving the shape and coordinate data from the bean
> - Setting the value of the hidden tag to the `id` of this component
> - Rendering the `area` tag, including the JavaScript for the `onmouseover`, `onmouseout`, and `onclick` functions

> Although these tasks are actually performed by `AreaRenderer`, `AreaComponent` must delegate the tasks to `AreaRenderer`. See Delegating Rendering to a Renderer for more information.

> The rest of this section details how `MapRenderer` 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.

> ### Specifying the Component Family

> If your custom component class delegates rendering, it needs to override the getFamily method of UIComponent to return the identifier of a component family, which is used to refer to a component or set of components that can be rendered by a renderer or set of renderers. The component family is used along with the renderer type to look up renderers that can render the component.

> Because `MapComponent` delegates its rendering, it overrides the `getFamily` method:

>     public String getFamily() {
>       return ("Map");
>     }

> The component family identifier, `Map`, must match that defined by the `component-family` elements included in the component and renderer configurations in the application configuration resource file. Registering a Custom Renderer with a Render Kit explains how to define the component family in the renderer configuration. Registering a Custom Component explains how to define the component family in the component configuration.

> ### Performing Encoding

> During the render response phase, the JavaServer Faces implementation processes the encoding methods of all components and their associated renderers in the view. 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` instance contains all the information associated with the current request. The `UIComponent` argument is the component that needs to be rendered.

> The rest of the method renders 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, `map`). (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`, 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` instance 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` instance 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` instance 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` instance associated with the attribute. Because `MapComponent` extends `UICommand`, the `UICommand` class already does the work of getting the `ValueBinding` instance associated with each of the attributes that it supports. However, if you have a custom component class that extends `UIComponentBase`, you will need to get the `ValueBinding` instance 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` instance 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` object. For example, if `MapComponent` extended `UIComponentBase` instead of `UICommand`, it would need to provide an `action` property that returns and accepts a `MethodBinding` object:

>     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 components 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)` method 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 a `FacesContext` and an `Object` instance, 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 the deployment descriptor where you want the state to be saved: either client or server. If state is saved on the client, the state of the entire view is rendered to a hidden field on the page.

> To specify where state is saved for a particular Web application, you need to launch `deploytool`, select the Web application from the tree, and set its `javax.faces.STATE_SAVING_METHOD` context parameter to either client or server. See Specifying Where State Is Saved for more information on specifying where state is saved using `deploytool`.

Download PDF Home History |  | API Search Feedback FAQ

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.