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 fromUIComponentBase
. 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 thejavax.faces.component
package and their names begin withUI
.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 extendUISelectOne
rather thanUIComponentBase
because you can reuse the behavior already defined inUISelectOne
. 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 anActionEvent
ActionSource2
: ExtendsActionSource
and allows component properties referencing methods that handle action events to use method expressions as defined by the unified EL. This class was introduced in JavaServer Faces Technology 1.2.EditableValueHolder
: ExtendsValueHolder
and specifies additional features for editable components, such as validation and emitting value-change eventsNamingContainer
: Mandates that each component rooted at this component have a unique IDStateHolder
: Denotes that a component has state that must be saved between requestsValueHolder
: Indicates that the component maintains a local value as well as the option of accessing data in the model tierIf your component extends
UIComponentBase
, it automatically implements onlyStateHolder
. Because all components--directly or indirectly--extendUIComponentBase
, they all implementStateHolder
.If your component extends one of the other standard components, it might also implement other behavioral interfaces in addition to
StateHolder
. If your component extendsUICommand
, it automatically implementsActionSource2
. If your component extendsUIOutput
or one of the component classes that extendUIOutput
, it automatically implementsValueHolder
. If your component extendsUIInput
, it automatically implementsEditableValueHolder
andValueHolder
. See the JavaServer Faces API Javadoc to find out what the other component classes implement.You can also make your component explicitly implement a behavioral interface that it doesn't already by virtue of extending a particular standard component. For example, if you have a component that extends
UIInput
and you want it to fire action events, you must make it explicitly implementActionSource2
because aUIInput
component doesn't automatically implement this interface.The image map example has two component classes:
AreaComponent
andMapComponent
. TheMapComponent
class extendsUICommand
and therefore implementsActionSource2
, which means it can fire action events when a user clicks on the map. TheAreaComponent
class extends the standard componentUIOutput
.The
MapComponent
class represents the component corresponding to themap
tag:<bookstore:map id="worldMap" current="NAmericas" immediate="true" action="bookstore" actionListener="#{localeBean.chooseLocaleFromMap}">The
AreaComponent
class represents the component corresponding to thearea
tag:<bookstore:area id="NAmerica" value="#{NA}" onmouseover="/template/world_namer.jpg" onmouseout="/template/world.jpg" targetImage="mapImage" />
MapComponent
has one or moreAreaComponent
instances as children. Its behavior consists of the followingThe rendering of the
map
andinput
tags is performed byMapRenderer
, butMapComponent
delegates this rendering toMapRenderer
.
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 thevalue
expression in Creating the Renderer Class. The behavior ofAreaComponent
consists of the followingAlthough these tasks are actually performed by
AreaRenderer
,AreaComponent
must delegate the tasks toAreaRenderer
. See Delegating Rendering to a Renderer for more information.The rest of this section describes the tasks that
MapComponent
performs as well as the encoding and decoding that it delegates toMapRenderer
. Handling Events for Custom Components details howMapComponent
handles events.Specifying the Component Family
If your custom component class delegates rendering, it needs to override the
getFamily
method ofUIComponent
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 thegetFamily
method:The component family identifier,
Map
, must match that defined by thecomponent-family
elements included in the component and renderer configurations in the application configuration resource file. Registering a Custom Renderer with a Render Kit (page 466) explains how to define the component family in the renderer configuration. Registering a Custom Component (page 469) 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
, andencodeEnd
. 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 inencodeEnd
.Because
MapComponent
is a parent component ofAreaComponent
, thearea
tags must be rendered after the beginningmap
tag and before the endingmap
tag. To accomplish this, theMapRenderer
class renders the beginningmap
tag inencodeBegin
and the rest of themap
tag inencodeEnd
.The JavaServer Faces implementation automatically invokes the
encodeEnd
method ofAreaComponent
's renderer after it invokesMapRenderer
'sencodeBegin
method and before it invokesMapRenderer
'sencodeEnd
method. If a component needs to perform the rendering for its children, it does this in theencodeChildren
method.Here are the
encodeBegin
andencodeEnd
methods ofMapRenderer
: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 beginningmap
tag. TheencodeEnd
method renders theinput
tag and the endingmap
tag.The encoding methods accept a
UIComponent
argument and aFacesContext
argument. TheFacesContext
instance contains all the information associated with the current request. TheUIComponent
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 theResponseWriter
instance as strings, retrieving the values of the component attributes, and passing these values to theResponseWriter
instance.The
startElement
method takes aString
(the name of the tag) and the component to which the tag corresponds (in this case,map
). (Passing this information to theResponseWriter
instance helps design-time tools know which portions of the generated markup are related to which components.)After calling
startElement
, you can callwriteAttribute
to render the tag's attributes. ThewriteAttribute
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 benull
, and it won't be rendered.The
name
attribute value of themap
tag is retrieved using thegetId
method ofUIComponent
, which returns the component's unique identifier. Thename
attribute value of the input tag is retrieved using thegetName(FacesContext, UIComponent)
method ofMapRenderer
.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 there is a renderer available, this method invokes the superclass's
encodeEnd
method, which does the work of finding the renderer. TheMapComponent
class delegates all rendering toMapRenderer
, 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 thedecode
method.Performing Decoding
During the apply request values phase, the JavaServer Faces implementation processes the
decode
methods of all components in the tree. Thedecode
method extracts a component's local value from incoming request parameters and uses aConverter
class to convert 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 hiddeninput
field and sets thecurrent
attribute to this value by using itsdecode
method. ThesetCurrent
method ofMapComponent
queues the event by callingqueueEvent
, passing in theAreaSelectedEvent
instance generated byMapComponent
.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 hiddeninput
field by callinggetName(FacesContext, UIComponent)
. It then uses that name as the key to the request parameter map to retrieve the current value of theinput
field. This value represents the currently selected area. Finally, it sets the value of theMapComponent
class'scurrent
attribute to the value of theinput
field.Enabling Component Properties to Accept Expressions
Nearly all the attributes of the standard JavaServer Faces tags can accept expressions, whether they are value expressions or a method expressions. It is recommended that you also enable your component attributes to accept expressions because this is what page authors expect, and it gives page authors much more flexibility when authoring their pages.
Creating the Component Tag Handler describes how
MapTag
, the tag handler for themap
tag, sets the component's values when processing the tag. It does this by providing the following:
- A method for each attribute that takes either a
ValueExpression
orMethodExpression
object depending on what kind of expression the attribute accepts.- A
setProperties
method that stores theValueExpression
orMethodExpression
object for each component property so that the component class can retrieve the expression object later.To retrieve the expression objects that
setProperties
stored, the component class must implement a method for each property that accesses the appropriate expression object, extracts the value from it and returns the value.Because
MapComponent
extendsUICommand
, theUICommand
class already does the work of getting theValueExpression
andMethodExpression
instances associated with each of the attributes that it supports.However, if you have a custom component class that extends
UIComponentBase
, you will need to implement the methods that get theValueExpression
andMethodExpression
instances associated with those attributes that are enabled to accept expressions. For example, ifMapComponent
extendedUIComponentBase
instead ofUICommand
, it would need to include a method that gets theValueExpression
instance for theimmediate
attribute:public boolean isImmediate() { if (this.immediateSet) { return (this.immediate); } ValueExpression ve = getValueExpression("immediate"); if (ve != null) { Boolean value = (Boolean) ve.getValue( getFacesContext().getELContext()); return (value.booleanValue()); } else { return (this.immediate); } }The properties corresponding to the component attributes that accept method expressions must accept and return a
MethodExpression
object. For example, ifMapComponent
extendedUIComponentBase
instead ofUICommand
, it would need to provide anaction
property that returns and accepts aMethodExpression
object:public MethodExpression getAction() { return (this.action); } public void setAction(MethodExpression action) { this.action = action; }
Saving and Restoring State
Because component classes implement
StateHolder
, they must implement thesaveState(FacesContext)
andrestoreState(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 fromMapComponent
: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 forrestoreState(FacesContext, Object)
, which restores the state of the component to that saved with thesaveState(FacesContext)
method. TherestoreState(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 therestoreState(FacesContext, Object)
method fromMapComponent
: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 anObject
instance, representing the array that is holding the state for the component. This method sets the component's properties to the values saved in theObject
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 set the
javax.faces.STATE_SAVING_METHOD
context parameter to either client or server in your application's deployment descriptor. See Saving and Restoring State for more information on specifying where state is saved in the deployment desciptor.