|
Download
FAQ History |
|
API
Search Feedback |
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
UIComponentBaseclass 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
UIComponentBasedirectly or extend a class representing one of the standard components. These classes are located in thejavax.faces.componentpackage 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 extendUISelectOnerather thanUIComponentBasebecause 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
UIComponentBaseor 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 anActionEventEditableValueHolder: ExtendsValueHolderand 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
UICommand, it automatically implementsActionSourceandStateHolder. If your component extendsUIOutputor one of the component classes that extendUIOutput, it automatically implementsStateHolderandValueHolder. If your component extendsUIInput, it automatically implementsEditableValueHolder,StateHolder, andValueHolder. If your component extendsUIComponentBase, it automatically implements onlyStateHolder. 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 implementActionSource.The image map example has two component classes:
AreaComponentandMapComponent. TheMapComponentclass extendsUICommand. TheAreaComponentclass extends the standard componentUIOutput.The
MapComponentclass represents the component corresponding to themaptag:<bookstore:map id="worldMap" current="NAmericas" immediate="true" action="storeFront" actionListener="#{localeBean.chooseLocaleFromMap}">The
AreaComponentclass represents the component corresponding to theareatag:<bookstore:area id="NAmerica" value="#{NA}" onmouseover="/template/world_namer.jpg" onmouseout="/template/world.jpg" targetImage="mapImage" />
MapComponenthas one or moreAreaComponents as children. Its behavior consists of the followingThe rendering of the
mapandinputtags is actually performed by thebookstore6/src/renderers/MapRenderer, butMapComponentdelegates this rendering toMapRenderer.Because
MapComponentextendsUICommand, it automatically implementsActionSourceso that it can fire anActionEventwhen a user clicks on the map.The
AreaComponentclass extendsUIOutputbecauseAreaComponentrequires avalueattribute, which is already defined byUIOutput.
AreaComponentis 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 thevalueexpression in Create the Renderer Class. The behavior of theAreaComponentcomponent consists of the followingAlthough these tasks are actually performed by
bookstore6/src/renderers/AreaRenderer, theAreaComponentclass must delegate the tasks toAreaRenderer. See Delegating Rendering to a Renderer for more information.The rest of this section details how the
MapRendererclass performs encoding and decoding, how it defines properties for the component's local values, and how it saves the state ofMapComponent. Handling Events for Custom Components details howMapComponenthandles 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
UIComponentBaseclass 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
MapComponentis a parent component ofAreaComponent, theareatags must be rendered after the beginningmaptag and before the endingmaptag. To accomplish this, theMapRendererclass renders the beginningmaptag inencodeBeginand the rest of themaptag inencodeEnd.The JavaServer Faces implementation automatically invokes the
encodeEndmethod ofAreaComponent's renderer after it invokesMapRenderer'sencodeBeginmethod and before it invokesMapRenderer'sencodeEndmethod. If a component needs to perform the rendering for its children, it does this in theencodeChildrenmethod.Here are the
encodeBeginandencodeEndmethods 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
encodeBeginrenders only the beginningmaptag. TheencodeEndmethod renders theinputtag and the endingmaptag.The encoding methods accept a
UIComponentargument and aFacesContextargument. TheFacesContextcontains all the information associated with the current request. TheUIComponentargument 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 theResponseWriteras strings, retrieving the values of the component attributes, and passing these values to theResponseWriter.The
startElementmethod takes aString(the name of the tag) and the component to which the tag corresponds (in this case,map). (Passing this information to theResponseWriterhelps design-time tools know which portions of the generated markup are related to which components.)After calling
startElement, you can callwriteAttributeto render the tag's attributes. ThewriteAttributemethod 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
nameattribute value of themaptag is retrieved using thegetIdmethod ofUIComponent, which returns the component's unique identifier. Thenameattribute 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
encodeEndmethod, which does the work of finding the renderer. TheMapComponentclass 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 thedecodemethod.Performing Decoding
During the apply request values phase, the JavaServer Faces implementation processes the
decodemethods of all components in the tree. Thedecodemethod 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
MapRendererretrieves the local value of the hiddeninputfield and sets thecurrentattribute to this value by using itsdecodemethod. ThesetCurrentmethod ofMapComponentqueues the event by callingqueueEvent, passing in theAreaSelectedEventgenerated 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
decodemethod first gets the name of the hiddeninputfield by callinggetName(FacesContext, UIComponent). It then uses that name as the key to the request parameter map to retrieve the current value of theinputfield. This value represents the currently selected area. Finally, it sets the value of theMapComponentclass'scurrentattribute to the value of theinputfield.Enabling Value-Binding of Component Properties
Creating the Component Tag Handler describes how
MapTagsets the component's values when processing the tag. For those component attributes that take value-binding expressions that point to a backing bean property,MapTaguses aValueBindingto 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
ValueBindingassociated with the attribute. BecauseMapComponentextendsUICommand, theUICommandalready does the work of getting theValueBindingassociated with each of the attributes that it supports. However, if you have a custom component that extendsUIComponentBase, you will need to get theValueBindingassociated with those attributes that are value-binding enabled. For example, ifMapComponentextendedUIComponentBaseinstead ofUICommand, it would need to include a method that gets theValueBindingfor theimmediateattribute: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, ifMapComponentextendedUIComponentBaseinstead ofUICommand, it would need to provide anactionproperty that returns and accepts aMethodBinding: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 thesaveState(FacesContext)andrestoreState(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 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
StateHoldermust also provide an implementation forrestoreState(FacesContext, Object), which restores the state of the component to that saved with thesaveState(FacesContext)method. TherestoreState(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 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 the
FacesContextand theObject, representing the array that is holding the state for the component. This method sets the component's properties to the values saved in theObjectarray.When you implement these methods in your component class, be sure to specify in your
web.xmlfile where you want the state to be saved: either client or server. Here is thecontext-paramelement from the components demoweb.xmlfile. 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.
|
Download
FAQ History |
|
API
Search Feedback |
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.