Creating Custom UI Components and Other Custom Objects
We are working on a fresh, updated Jakarta EE Tutorial. This section hasn’t yet been updated. |
This chapter describes creating custom components for applications that have additional functionality not provided by standard Jakarta Faces components.
Introduction to Creating Custom Components
Jakarta Faces technology offers a basic set of standard, reusable UI components that enable quick and easy construction of user interfaces for web applications. These components mostly map one-to-one to the elements in HTML 4. But often an application requires a component that has additional functionality or requires a completely new component. Jakarta Faces technology allows extension of standard components to enhance their functionality or to create custom components. A rich ecosystem of third-party component libraries is built on this extension capability, but it is beyond the scope of this tutorial to examine them. A web search for "Faces Component Libraries" (or "JSF Component Libraries") is a good starting point to learn more about this important aspect of using Jakarta Faces technology.
In addition to extending the functionality of standard components, a component writer might want to give a page author the ability to change the appearance of the component on the page or to alter listener behavior. Alternatively, the component writer might want to render a component to a different kind of client device type, such as a smartphone or a tablet instead of a desktop computer. Enabled by the flexible Jakarta Faces architecture, a component writer can separate the definition of the component behavior from its appearance by delegating the rendering of the component to a separate renderer. In this way, a component writer can define the behavior of a custom component once but create multiple renderers, each of which defines a different way to render the component to a particular kind of client device.
A jakarta.faces.component.UIComponent
is a Java class that is responsible for representing a self-contained piece of the user interface during the request-processing lifecycle.
It is intended to represent the meaning of the component; the visual representation of the component is the responsibility of the jakarta.faces.render.Renderer
.
There can be multiple instances of the same UIComponent
class in any given Jakarta Faces view, just as there can be multiple instances of any Java class in any given Java program.
Jakarta Faces technology provides the ability to create custom components by extending the UIComponent
class, the base class for all standard UI components.
A custom component can be used anywhere an ordinary component can be used, such as within a composite component.
A UIComponent
is identified by two names: component-family
specifies the general purpose of the component (input or output, for instance), and component-type
indicates the specific purpose of a component, such as a text input field or a command button.
A Renderer
is a helper to the UIComponent
that deals with how that specific UIComponent
class should appear in a specific kind of client device.
Renderers are identified by two names: render-kit-id
and renderer-type
.
A render kit is just a bucket into which a particular group of renderers is placed, and the render-kit-id
identifies the group.
Most Jakarta Faces component libraries provide their own render kits.
A jakarta.faces.view.facelets.Tag
object is a helper to the UIComponent
and Renderer
that allows the page author to include an instance of a UIComponent
in a Jakarta Faces view.
A tag represents a specific combination of component-type
and renderer-type
.
See Component, Renderer, and Tag Combinations for information on how components, renderers, and tags interact.
This chapter uses the image map component from the Duke’s Bookstore case study example to explain how you can create simple custom components, custom renderers, and associated custom tags, and take care of all the other details associated with using the components and renderers in an application. See Duke’s Bookstore Case Study Example for more information about this example.
The chapter also describes how to create other custom objects: custom converters, custom listeners, and custom validators. It also describes how to bind component values and instances to data objects and how to bind custom objects to managed bean properties.
Determining Whether You Need a Custom Component or Renderer
The Jakarta Faces implementation supports a very basic set of components and associated renderers. This section helps you to decide whether you can use standard components and renderers in your application or need a custom component or custom renderer.
When to Use a Custom Component
A component class defines the state and behavior of a UI component. This behavior includes converting the value of a component to the appropriate markup, queuing events on components, performing validation, and any other behavior related to how the component interacts with the browser and the request-processing lifecycle.
You need to create a custom component in the following situations.
-
You need to add new behavior to a standard component, such as generating an additional type of event (for example, notifying another part of the page that something changed in this component as a result of user interaction).
-
You need to take a different action in the request processing of the value of a component from what is available in any of the existing standard components.
-
You want to take advantage of an HTML capability offered by your target browser, but none of the standard Jakarta Faces components take advantage of the capability in the way you want, if at all. The current release does not contain standard components for complex HTML components, such as frames; however, because of the extensibility of the component architecture, you can use Jakarta Faces technology to create components like these. The Duke’s Bookstore case study creates custom components that correspond to the HTML
map
andarea
tags. -
You need to render to a non-HTML client that requires extra components not supported by HTML. Eventually, the standard HTML render kit will provide support for all standard HTML components. However, if you are rendering to a different client, such as a phone, you might need to create custom components to represent the controls uniquely supported by the client. For example, some component architectures for wireless clients include support for tickers and progress bars, which are not available on an HTML client. In this case, you might also need a custom renderer along with the component, or you might need only a custom renderer.
You do not need to create a custom component in the following cases.
-
You need to aggregate components to create a new component that has its own unique behavior. In this situation, you can use a composite component to combine existing standard components. For more information on composite components, see Composite Components and Composite Components: Advanced Topics and an Example.
-
You simply need to manipulate data on the component or add application-specific functionality to it. In this situation, you should create a managed bean for this purpose and bind it to the standard component rather than create a custom component. See Managed Beans in Jakarta Faces Technology for more information on managed beans.
-
You need to convert a component’s data to a type not supported by its renderer. See Using the Standard Converters for more information about converting a component’s data.
-
You need to perform validation on the component data. Standard validators and custom validators can be added to a component by using the validator tags from the page. See Using the Standard Validators and Creating and Using a Custom Validator for more information about validating a component’s data.
-
You need to register event listeners on components. You can either register event listeners on components using the
f:valueChangeListener
andf:actionListener
tags, or you can point at an event-processing method on a managed bean using the component’sactionListener
orvalueChangeListener
attributes. See Implementing an Event Listener and Writing Managed Bean Methods for more information.
When to Use a Custom Renderer
A renderer, which generates the markup to display a component on a web page, allows you to separate the semantics of a component from its appearance. By keeping this separation, you can support different kinds of client devices with the same kind of authoring experience. You can think of a renderer as a "client adapter." It produces output suitable for consumption and display by the client and accepts input from the client when the user interacts with that component.
If you are creating a custom component, you need to ensure, among other things, that your component class performs these operations that are central to rendering the component:
-
Decoding: Converting the incoming request parameters to the local value of the component
-
Encoding: Converting the current local value of the component into the corresponding markup that represents it in the response
The Jakarta Faces specification supports two programming models for handling encoding and decoding.
-
Direct implementation: The component class itself implements the decoding and encoding.
-
Delegated implementation: The component class delegates the implementation of encoding and decoding to a separate renderer.
By delegating the operations to the renderer, you have the option of associating your custom component with different renderers so that you can render the component on different clients. If you don’t plan to render a particular component on different clients, it may be simpler to let the component class handle the rendering. However, a separate renderer enables you to preserve the separation of semantics from appearance. The Duke’s Bookstore application separates the renderers from the components, although it renders only to HTML 4 web browsers.
If you aren’t sure whether you will need the flexibility offered by separate renderers but you want to use the simpler direct-implementation approach, you can actually use both models. Your component class can include some default rendering code, but it can delegate rendering to a renderer if there is one.
Component, Renderer, and Tag Combinations
When you create a custom component, you can create a custom renderer to go with it. To associate the component with the renderer and to reference the component from the page, you will also need a custom tag.
Although you need to write the custom component and renderer, there is no need to write code for a custom tag (called a tag handler). If you specify the component and renderer combination, Facelets creates the tag handler automatically.
In rare situations, you might use a custom renderer with a standard component rather than a custom component. Or you might use a custom tag without a renderer or a component. This section gives examples of these situations and summarizes what is required for a custom component, renderer, and tag.
You would use a custom renderer without a custom component if you wanted to add some client-side validation on a standard component. You would implement the validation code with a client-side scripting language, such as JavaScript, and then render the JavaScript with the custom renderer. In this situation, you need a custom tag to go with the renderer so that its tag handler can register the renderer on the standard component.
Custom components as well as custom renderers need custom tags associated with them. However, you can have a custom tag without a custom renderer or custom component. For example, suppose that you need to create a custom validator that requires extra attributes on the validator tag. In this case, the custom tag corresponds to a custom validator and not to a custom component or custom renderer. In any case, you still need to associate the custom tag with a server-side object.
Requirements for Custom Components, Custom Renderers and Custom Tags summarizes what you must or can associate with a custom component, custom renderer, or custom tag.
Custom Item | Must Have | Can Have |
---|---|---|
Custom component |
Custom tag |
Custom renderer or standard renderer |
Custom renderer |
Custom tag |
Custom component or standard component |
Custom Jakarta Faces tag |
Some server-side object, like a component, a custom renderer, or custom validator |
Custom component or standard component associated with a custom renderer |
Understanding the Image Map Example
Duke’s Bookstore includes a custom image map component on the index.xhtml
page.
This image map displays a selection of six book titles.
When the user clicks one of the book titles in the image map, the application goes to a page that displays the title of the selected book as well as information about a featured book.
The page allows the user to add either book (or none) to the shopping cart.
Why Use Jakarta Faces Technology to Implement an Image Map?
Jakarta Faces technology is an ideal framework to use for implementing this kind of image map because it can perform the work that must be done on the server without requiring you to create a server-side image map.
In general, client-side image maps are preferred over server-side image maps for several reasons. One reason is that the client-side image map allows the browser to provide immediate feedback when a user positions the mouse over a hotspot. Another reason is that client-side image maps perform better because they don’t require round-trips to the server. However, in some situations, your image map might need to access the server to retrieve data or to change the appearance of nonform controls, tasks that a client-side image map cannot do.
Because the image map custom component uses Jakarta Faces technology, it has the best of both styles of image maps: It can handle the parts of the application that need to be performed on the server while allowing the other parts of the application to be performed on the client side.
Understanding the Rendered HTML
Here is an abbreviated version of the form part of the HTML page that the application needs to render:
<form id="j_idt13" name="j_idt13" method="post"
action="/dukesbookstore/index.xhtml" ...>
...
<img id="j_idt13:mapImage"
src="/dukesbookstore/jakarta.faces.resource/book_all.jpg?ln=images"
alt="Choose a Book from our Catalog"
usemap="#bookMap" />
...
<map name="bookMap">
<area alt="Duke"
coords="67,23,212,268"
shape="rect"
onmouseout="document.forms[0]['j_idt13:mapImage'].src='resources/images/book_all.jpg'"
onmouseover="document.forms[0]['j_idt13:mapImage'].src='resources/images/book_201.jpg'"
onclick="document.forms[0]['bookMap_current'].value='Duke'; document.forms[0].submit()"
/>
...
<input type="hidden" name="bookMap_current">
</map>
...
</form>
The img
tag associates an image (book_all.jpg
) with the image map referenced in the usemap
attribute value.
The map
tag specifies the image map and contains a set of area
tags.
Each area
tag specifies a region of the image map.
The onmouseover
, onmouseout
, and onclick
attributes define which JavaScript code is executed when these events occur.
When the user moves the mouse over a region, the onmouseover
function associated with the region displays the map with that region highlighted.
When the user moves the mouse out of a region, the onmouseout
function redisplays the original image.
If the user clicks on a region, the onclick
function sets the value of the input
tag to the ID of the selected area and submits the page.
The input
tag represents a hidden control that stores the value of the currently selected area between client-server exchanges so that the server-side component classes can retrieve the value.
The server-side objects retrieve the value of bookMap_current
and set the locale in the jakarta.faces.context.FacesContext
instance according to the region that was selected.
Understanding the Facelets Page
Here is an abbreviated form of the Facelets page that the image map component uses to generate the HTML page shown in the preceding section.
It uses custom bookstore:map
and bookstore:area
tags to represent the custom components:
<h:form>
...
<h:graphicImage id="mapImage"
name="book_all.jpg"
library="images"
alt="#{bundle.ChooseBook}"
usemap="#bookMap" />
<bookstore:map id="bookMap"
current="map1"
immediate="true"
action="bookstore">
<f:actionListener
type="dukesbookstore.listeners.MapBookChangeListener" />
<bookstore:area id="map1" value="#{Book201}"
onmouseover="resources/images/book_201.jpg"
onmouseout="resources/images/book_all.jpg"
targetImage="mapImage" />
<bookstore:area id="map2" value="#{Book202}"
onmouseover="resources/images/book_202.jpg"
onmouseout="resources/images/book_all.jpg"
targetImage="mapImage"/>
...
</bookstore:map>
...
</h:form>
The alt
attribute of the h:graphicImage
tag maps to the localized string "Choose a Book from our Catalog"
.
The f:actionListener
tag within the bookstore:map
tag points to a listener class for an action event.
The processAction
method of the listener places the book ID for the selected map area into the session map.
The way this event is handled is explained more in Handling Events for Custom Components.
The action
attribute of the bookstore:map
tag specifies a logical outcome String
, "bookstore"
, which by implicit navigation rules sends the application to the page bookstore.xhtml
.
For more information on navigation, see Configuring Navigation Rules.
The immediate
attribute of the bookstore:map
tag is set to true
, which indicates that the default jakarta.faces.event.ActionListener
implementation should execute during the Apply Request Values phase of the request-processing lifecycle, instead of waiting for the Invoke Application phase.
Because the request resulting from clicking the map does not require any validation, data conversion, or server-side object updates, it makes sense to skip directly to the Invoke Application phase.
The current
attribute of the bookstore:map
tag is set to the default area, which is map1
(the book My Early Years: Growing Up on Star7, by Duke).
Notice that the bookstore:area
tags do not contain any of the JavaScript, coordinate, or shape data that is displayed on the HTML page.
The JavaScript is generated by the dukesbookstore.renderers.AreaRenderer
class.
The onmouseover
and onmouseout
attribute values indicate the image to be loaded when these events occur.
How the JavaScript is generated is explained more in Performing Encoding.
The coordinate, shape, and alternate text data are obtained through the value
attribute, whose value refers to an attribute in application scope.
The value of this attribute is a bean, which stores the coords
, shape
, and alt
data.
How these beans are stored in the application scope is explained more in the next section.
Summary of the Image Map Application Classes
Image Map Classes summarizes all the classes needed to implement the image map component.
Class | Function |
---|---|
|
The |
|
The class that defines |
|
The class that defines |
|
This |
|
The bean that stores the shape and coordinates of the hotspots. |
|
The action listener for the |
The Duke’s Bookstore source directory, called bookstore-dir, is jakartaee-examples/tutorial/case-studies/dukes-bookstore/src/main/java/jakarta/tutorial/dukesbookstore/
.
The event and listener classes are located in bookstore-dir/listeners/
.
The component classes are located in bookstore-dir/components/
.
The renderer classes are located in bookstore-dir/renderers/
.
ImageArea
is located in bookstore-dir/model/
.
Steps for Creating a Custom Component
You can apply the following steps while developing your own custom component.
-
Create a custom component class that does the following:
-
Overrides the
getFamily
method to return the component family, which is used to look up renderers that can render the component -
Includes the rendering code or delegates it to a renderer (explained in Step 2)
-
Enables component attributes to accept expressions
-
Queues an event on the component if the component generates events
-
Saves and restores the component state
-
-
Delegate rendering to a renderer if your component does not handle the rendering. To do this:
-
Create a custom renderer class by extending
jakarta.faces.render.Renderer
. -
Register the renderer to a render kit.
-
-
Register the component.
-
Create an event handler if your component generates events.
-
Create a tag library descriptor (TLD) that defines the custom tag.
See Registering a Custom Component and Registering a Custom Renderer with a Render Kit for information on registering the custom component and the renderer. The section Using a Custom Component discusses how to use the custom component in a Jakarta Faces page.
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 jakarta.faces.component.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 jakarta.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 defined in the jakarta.faces.component
package:
-
ActionSource
: Indicates that the component can fire ajakarta.faces.event.ActionEvent
-
ActionSource2
: ExtendsActionSource
and allows component properties referencing methods that handle action events to use method expressions as defined by the EL -
EditableValueHolder
: ExtendsValueHolder
and specifies additional features for editable components, such as validation and emitting value-change events -
NamingContainer
: Mandates that each component rooted at this component has 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 UIComponentBase
, it automatically implements only StateHolder
.
Because all components directly or indirectly extend UIComponentBase
, they all implement StateHolder
.
Any component that implements StateHolder
also implements the StateHelper
interface, which extends StateHolder
and defines a Map
-like contract that makes it easy for components to save and restore a partial view state.
If your component extends one of the other standard components, it might also implement other behavioral interfaces in addition to StateHolder
.
If your component extends UICommand
, it automatically implements ActionSource2
.
If your component extends UIOutput
or one of the component classes that extend UIOutput
, it automatically implements ValueHolder
.
If your component extends UIInput
, it automatically implements EditableValueHolder
and ValueHolder
.
See the Jakarta Faces API documentation 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 implement ActionSource2
because a UIInput
component doesn’t automatically implement this interface.
The Duke’s Bookstore image map example has two component classes: AreaComponent
and MapComponent
.
The MapComponent
class extends UICommand
and therefore implements ActionSource2
, which means it can fire action events when a user clicks on the map.
The AreaComponent
class extends the standard component UIOutput
.
The @FacesComponent
annotation registers the components with the Jakarta Faces implementation:
@FacesComponent("DemoMap")
public class MapComponent extends UICommand {...}
@FacesComponent("DemoArea")
public class AreaComponent extends UIOutput {...}
The MapComponent
class represents the component corresponding to the bookstore:map
tag:
<bookstore:map id="bookMap"
current="map1"
immediate="true"
action="bookstore">
...
</bookstore:map>
The AreaComponent
class represents the component corresponding to the bookstore:area
tag:
<bookstore:area id="map1" value="#{Book201}"
onmouseover="resources/images/book_201.jpg"
onmouseout="resources/images/book_all.jpg"
targetImage="mapImage"/>
MapComponent
has one or more AreaComponent
instances as children.
Its behavior consists of the following actions:
-
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 HTML
map
tag and the HTMLinput
tag
MapComponent
delegates the rendering of the HTML map
and input
tags to the MapRenderer
class.
AreaComponent
is bound to a bean that stores the shape and coordinates of the region of the image map.
You will 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 theonmouseover
,onmouseout
, andonclick
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 describes the tasks that MapComponent
performs as well as the encoding and decoding that it delegates to MapRenderer
.
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:
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 Jakarta 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
.
Alternatively, you can use the encodeALL
method, which encompasses all the methods.
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 Jakarta 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
:
@Override
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");
}
@Override
public void encodeEnd(FacesContext context, UIComponent component)
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 jakarta.faces.context.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 jakarta.faces.context.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, you must also implement the decode
method.
Performing Decoding
During the Apply Request Values phase, the Jakarta 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 uses a jakarta.faces.convert.Converter
implementation 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 component queues the event by calling queueEvent
.
Here is the decode
method of MapRenderer
:
@Override
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 Component Properties to Accept Expressions
Nearly all the attributes of the standard Jakarta Faces tags can accept expressions, whether they are value expressions or method expressions. It is recommended that you also enable your component attributes to accept expressions because it gives you much more flexibility when you write Facelets pages.
To enable the attributes to accept expressions, the component class must implement getter and setter methods for the component properties.
These methods can use the facilities offered by the StateHelper
interface to store and retrieve not only the values for these properties but also the state of the components across multiple requests.
Because MapComponent
extends UICommand
, the UICommand
class already does the work of getting the ValueExpression
and MethodExpression
instances associated with each of the attributes that it supports.
Similarly, the UIOutput
class that AreaComponent
extends already obtains the ValueExpression
instances for its supported attributes.
For both components, the simple getter and setter methods store and retrieve the key values and state for the attributes, as shown in this code fragment from AreaComponent
:
enum PropertyKeys {
alt, coords, shape, targetImage;
}
public String getAlt() {
return (String) getStateHelper().eval(PropertyKeys.alt, null);
}
public void setAlt(String alt) {
getStateHelper().put(PropertyKeys.alt, alt);
}
...
However, if you have a custom component class that extends UIComponentBase
, you will need to implement the methods that get the ValueExpression
and MethodExpression
instances associated with those attributes that are enabled to accept expressions.
For example, you could include a method that gets the ValueExpression
instance for the immediate
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, if MapComponent
extended UIComponentBase
instead of UICommand
, it would need to provide an action
property that returns and accepts a MethodExpression
object:
public MethodExpression getAction() {
return (this.action);
}
public void setAction(MethodExpression action) {
this.action = action;
}
Saving and Restoring State
As described in Enabling Component Properties to Accept Expressions, use of the StateHelper
interface facilities allows you to save the component’s state at the same time you set and retrieve property values.
The StateHelper
implementation allows partial state saving; it saves only the changes in the state since the initial request, not the entire state, because the full state can be restored during the Restore View phase.
Component classes that implement StateHolder
may prefer to implement the saveState(FacesContext)
and restoreState(FacesContext, Object)
methods to help the Jakarta Faces implementation save and restore the state of components across multiple requests.
To save a set of values, you can 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 a hypothetical method from MapComponent
, which has only one attribute, current
:
@Override
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 the component.
A component that implements StateHolder
may 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 Jakarta 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 a hypothetical 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.
Whether or not you implement these methods in your component class, you can use the jakarta.faces.STATE_SAVING_METHOD
context parameter 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.
By default, the state is saved on the server.
The web applications in the Duke’s Forest case study save their view state on the client.
Saving state on the client uses more bandwidth as well as more client resources, whereas saving it on the server uses more server resources. You may also want to save state on the client if you expect your users to disable cookies.
Delegating Rendering to a Renderer
Both MapComponent
and AreaComponent
delegate all of their rendering to a separate renderer.
The section Performing Encoding explains how MapRenderer
performs the encoding for MapComponent
.
This section explains in detail the process of delegating rendering to a renderer using AreaRenderer
, which performs the rendering for AreaComponent
.
To delegate rendering, you perform the tasks described in the following topics:
Creating the Renderer Class
When delegating rendering to a renderer, you can delegate all encoding and decoding to the renderer, or you can choose to do part of it in the component class.
The AreaComponent
class delegates encoding to the AreaRenderer
class.
The renderer class begins with a @FacesRenderer
annotation:
@FacesRenderer(componentFamily = "Area", rendererType = "DemoArea")
public class AreaRenderer extends Renderer { }
The @FacesRenderer
annotation registers the renderer class with the Jakarta Faces implementation as a renderer class.
The annotation identifies the component family as well as the renderer type.
To perform the rendering for AreaComponent
, AreaRenderer
must implement an encodeEnd
method.
The encodeEnd
method of AreaRenderer
retrieves the shape, coordinates, and alternative text values stored in the ImageArea
bean that is bound to AreaComponent
.
Suppose that the area
tag currently being rendered has a value
attribute value of "book203"
.
The following line from encodeEnd
gets the value of the attribute "book203"
from the FacesContext
instance:
ImageArea ia = (ImageArea)area.getValue();
The attribute value is the ImageArea
bean instance, which contains the shape
, coords
, and alt
values associated with the book203
AreaComponent
instance.
After retrieving the ImageArea
object, the method renders the values for shape
, coords
, and alt
by simply calling the associated accessor methods and passing the returned values to the ResponseWriter
instance, as shown by these lines of code, which write out the shape and coordinates:
writer.startElement("area", area);
writer.writeAttribute("alt", iarea.getAlt(), "alt");
writer.writeAttribute("coords", iarea.getCoords(), "coords");
writer.writeAttribute("shape", iarea.getShape(), "shape");
The encodeEnd
method also renders the JavaScript for the onmouseout
, onmouseover
, and onclick
attributes.
The Facelets page needs to provide only the path to the images that are to be loaded during an onmouseover
or onmouseout
action:
<bookstore:area id="map3" value="#{Book203}"
onmouseover="resources/images/book_203.jpg"
onmouseout="resources/images/book_all.jpg"
targetImage="mapImage"/>
The AreaRenderer
class takes care of generating the JavaScript for these actions, as shown in the following code from encodeEnd
.
The JavaScript that AreaRenderer
generates for the onclick
action sets the value of the hidden field to the value of the current area’s component ID and submits the page.
sb = new StringBuffer("document.forms[0]['").append(targetImageId).
append("'].src='");
sb.append(
getURI(context,
(String) area.getAttributes().get("onmouseout")));
sb.append("'");
writer.writeAttribute("onmouseout", sb.toString(), "onmouseout");
sb = new StringBuffer("document.forms[0]['").append(targetImageId).
append("'].src='");
sb.append(
getURI(context,
(String) area.getAttributes().get("onmouseover")));
sb.append("'");
writer.writeAttribute("onmouseover", sb.toString(), "onmouseover");
sb = new StringBuffer("document.forms[0]['");
sb.append(getName(context, area));
sb.append("'].value='");
sb.append(iarea.getAlt());
sb.append("'; document.forms[0].submit()");
writer.writeAttribute("onclick", sb.toString(), "value");
writer.endElement("area");
By submitting the page, this code causes the Jakarta Faces lifecycle to return back to the Restore View phase.
This phase saves any state information, including the value of the hidden field, so that a new request component tree is constructed.
This value is retrieved by the decode
method of the MapComponent
class.
This decode method is called by the Jakarta Faces implementation during the Apply Request Values phase, which follows the Restore View phase.
In addition to the encodeEnd
method, AreaRenderer
contains an empty constructor.
This is used to create an instance of AreaRenderer
so that it can be added to the render kit.
The @FacesRenderer
annotation registers the renderer class with the Jakarta Faces implementation as a renderer class.
The annotation identifies the component family as well as the renderer type.
Identifying the Renderer Type
Register the renderer with a render kit by using the @FacesRenderer
annotation (or by using the application configuration resource file, as explained in Registering a Custom Renderer with a Render Kit).
During the Render Response phase, the Jakarta Faces implementation calls the getRendererType
method of the component’s tag handler to determine which renderer to invoke, if there is one.
You identify the type associated with the renderer in the rendererType
element of the @FacesRenderer
annotation for AreaRenderer
as well as in the renderer-type
element of the tag library descriptor.
Implementing an Event Listener
The Jakarta Faces technology supports action events and value-change events for components.
Action events occur when the user activates a component that implements jakarta.faces.component.ActionSource
.
These events are represented by the class jakarta.faces.event.ActionEvent
.
Value-change events occur when the user changes the value of a component that implements jakarta.faces.component.EditableValueHolder
.
These events are represented by the class jakarta.faces.event.ValueChangeEvent
.
One way to handle events is to implement the appropriate listener classes.
Listener classes that handle the action events in an application must implement the interface jakarta.faces.event.ActionListener
.
Similarly, listeners that handle the value-change events must implement the interface jakarta.faces.event.ValueChangeListener
.
This section explains how to implement the two listener classes.
To handle events generated by custom components, you must implement an event listener and an event handler and manually queue the event on the component. See Handling Events for Custom Components for more information.
You do not need to create an ActionListener implementation to handle an event that results solely in navigating to a page and does not perform any other application-specific processing.
See Writing a Method to Handle Navigation for information on how to manage page navigation.
|
Implementing Value-Change Listeners
A jakarta.faces.event.ValueChangeListener
implementation must include a processValueChange(ValueChangeEvent)
method.
This method processes the specified value-change event and is invoked by the Jakarta Faces implementation when the value-change event occurs.
The ValueChangeEvent
instance stores the old and the new values of the component that fired the event.
In the Duke’s Bookstore case study, the NameChanged
listener implementation is registered on the name
UIInput
component on the bookcashier.xhtml
page.
This listener stores into session scope the name the user entered in the field corresponding to the name component.
The bookreceipt.xhtml
subsequently retrieves the name from the session scope:
<h:outputFormat title="thanks"
value="#{bundle.ThankYouParam}">
<f:param value="#{sessionScope.name}"/>
</h:outputFormat>
When the bookreceipt.xhtml
page is loaded, it displays the name inside the message:
"Thank you, {0}, for purchasing your books from us."
Here is part of the NameChanged
listener implementation:
public class NameChanged extends Object implements ValueChangeListener {
@Override
public void processValueChange(ValueChangeEvent event)
throws AbortProcessingException {
if (null != event.getNewValue()) {
FacesContext.getCurrentInstance().getExternalContext().
getSessionMap().put("name", event.getNewValue());
}
}
}
When the user enters the name in the field, a value-change event is generated, and the processValueChange(ValueChangeEvent)
method of the NameChanged
listener implementation is invoked.
This method first gets the ID of the component that fired the event from the ValueChangeEvent
object, and it puts the value, along with an attribute name, into the session map of the FacesContext
instance.
Registering a Value-Change Listener on a Component explains how to register this listener onto a component.
Implementing Action Listeners
A jakarta.faces.event.ActionListener
implementation must include a processAction(ActionEvent)
method.
The processAction(ActionEvent)
method processes the specified action event.
The Jakarta Faces implementation invokes the processAction(ActionEvent)
method when the ActionEvent
occurs.
The Duke’s Bookstore case study uses two ActionListener
implementations, LinkBookChangeListener
and MapBookChangeListener
.
See Handling Events for Custom Components for details on MapBookChangeListener
.
Registering an Action Listener on a Component explains how to register this listener onto a component.
Handling Events for Custom Components
As explained in Implementing an Event Listener, events are automatically queued on standard components that fire events.
A custom component, on the other hand, must manually queue events from its decode
method if it fires events.
Performing Decoding explains how to queue an event on MapComponent
using its decode
method.
This section explains how to write the class that represents the event of clicking on the map and how to write the method that processes this event.
As explained in Understanding the Facelets Page, the actionListener
attribute of the bookstore:map
tag points to the MapBookChangeListener
class.
The listener class’s processAction
method processes the event of clicking the image map.
Here is the processAction
method:
@Override
public void processAction(ActionEvent actionEvent)
throws AbortProcessingException {
AreaSelectedEvent event = (AreaSelectedEvent) actionEvent;
String current = event.getMapComponent().getCurrent();
FacesContext context = FacesContext.getCurrentInstance();
String bookId = books.get(current);
context.getExternalContext().getSessionMap().put("bookId", bookId);
}
When the Jakarta Faces implementation calls this method, it passes in an ActionEvent
object that represents the event generated by clicking on the image map.
Next, it casts it to an AreaSelectedEvent
object (see jakartaee-examples/tutorial/case-studies/dukes-bookstore/src/main/java/jakarta/tutorial/dukesbookstore/listeners/AreaSelectedEvent.java
).
Then this method gets the MapComponent
associated with the event.
Next, it gets the value of the MapComponent
object’s current
attribute, which indicates the currently selected area.
The method then uses the value of the current
attribute to get the book’s ID value from a HashMap
object, which is constructed elsewhere in the MapBookChangeListener
class.
Finally, the method places the ID obtained from the HashMap
object into the session map for the application.
In addition to the method that processes the event, you need the event class itself.
This class is very simple to write; you have it extend ActionEvent
and provide a constructor that takes the component on which the event is queued and a method that returns the component.
Here is the AreaSelectedEvent
class used with the image map:
public class AreaSelectedEvent extends ActionEvent {
public AreaSelectedEvent(MapComponent map) {
super(map);
}
public MapComponent getMapComponent() {
return ((MapComponent) getComponent());
}
}
As explained in the section Creating Custom Component Classes, in order for MapComponent
to fire events in the first place, it must implement ActionSource
.
Because MapComponent
extends UICommand
, it also implements ActionSource
.
Defining the Custom Component Tag in a Tag Library Descriptor
To use a custom tag, you declare it in a Tag Library Descriptor (TLD). The TLD file defines how the custom tag is used in a Jakarta Faces page. The web container uses the TLD to validate the tag. The set of tags that are part of the HTML render kit are defined in the HTML_BASIC TLD, available in the Jakarta Faces standard HTML tag library.
The TLD file name must end with taglib.xml
.
In the Duke’s Bookstore case study, the custom tags area
and map
are defined in the file web/WEB-INF/bookstore.taglib.xml
.
All tag definitions must be nested inside the facelet-taglib
element in the TLD.
Each tag is defined by a tag
element.
Here are the tag definitions for the area
and map
components:
<facelet-taglib xmlns="https://jakarta.ee/xml/ns/jakartaee"
...>
<namespace>http://dukesbookstore</namespace>
<tag>
<tag-name>area</tag-name>
<component>
<component-type>DemoArea</component-type>
<renderer-type>DemoArea</renderer-type>
</component>
</tag>
<tag>
<tag-name>map</tag-name>
<component>
<component-type>DemoMap</component-type>
<renderer-type>DemoMap</renderer-type>
</component>
</tag>
</facelet-taglib>
The component-type
element specifies the name defined in the @FacesComponent
annotation, and the renderer-type
element specifies the rendererType
defined in the @FacesRenderer
annotation.
The facelet-taglib
element must also include a namespace
element, which defines the namespace to be specified in pages that use the custom component.
See Using a Custom Component for information on specifying the namespace in pages.
The TLD file is located in the WEB-INF
directory.
In addition, an entry is included in the web deployment descriptor (web.xml
) to identify the custom tag library descriptor file, as follows:
<context-param>
<param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/bookstore.taglib.xml</param-value>
</context-param>
Using a Custom Component
To use a custom component in a page, you add the custom tag associated with the component to the page.
As explained in Defining the Custom Component Tag in a Tag Library Descriptor, you must ensure that the TLD that defines any custom tags is packaged in the application if you intend to use the tags in your pages.
TLD files are stored in the WEB-INF/
directory or subdirectory of the WAR file or in the META-INF/
directory or subdirectory of a tag library packaged in a JAR file.
You also need to include a namespace declaration in the page so that the page has access to the tags.
The custom tags for the Duke’s Bookstore case study are defined in bookstore.taglib.xml
.
The ui:composition
tag on the index.xhtml
page declares the namespace defined in the tag library:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core"
xmlns:bookstore="http://dukesbookstore"
template="./bookstoreTemplate.xhtml">
Finally, to use a custom component in a page, you add the component’s tag to the page.
The Duke’s Bookstore case study includes a custom image map component on the index.xhtml
page.
This component allows you to select a book by clicking on a region of the image map:
...
<h:graphicImage id="mapImage"
name="book_all.jpg"
library="images"
alt="#{bundle.chooseLocale}"
usemap="#bookMap" />
<bookstore:map id="bookMap"
current="map1"
immediate="true"
action="bookstore">
<f:actionListener
type="ee.jakarta.tutorial.dukesbookstore.listeners.MapBookChangeListener" />
<bookstore:area id="map1" value="#{Book201}"
onmouseover="resources/images/book_201.jpg"
onmouseout="resources/images/book_all.jpg"
targetImage="mapImage" />
...
<bookstore:area id="map6" value="#{Book207}"
onmouseover="resources/images/book_207.jpg"
onmouseout="resources/images//book_all.jpg"
targetImage="mapImage" />
</bookstore:map>
The standard h:graphicImage
tag associates an image (book_all.jpg
) with an image map that is referenced in the usemap
attribute value.
The custom bookstore:map
tag that represents the custom component, MapComponent
, specifies the image map and contains a set of bookstore:area
tags.
Each custom bookstore:area
tag represents a custom AreaComponent
and specifies a region of the image map.
On the page, the onmouseover
and onmouseout
attributes specify the image that is displayed when the user performs the actions described by the attributes.
The custom renderer also renders an onclick
attribute.
In the rendered HTML page, the onmouseover
, onmouseout
, and onclick
attributes define which JavaScript code is executed when these events occur.
When the user moves the mouse over a region, the onmouseover
function associated with the region displays the map with that region highlighted.
When the user moves the mouse out of a region, the onmouseout
function redisplays the original image.
When the user clicks a region, the onclick
function sets the value of a hidden input
tag to the ID of the selected area and submits the page.
When the custom renderer renders these attributes in HTML, it also renders the JavaScript code.
The custom renderer also renders the entire onclick
attribute rather than letting the page author set it.
The custom renderer that renders the HTML map
tag also renders a hidden input
component that holds the current area.
The server-side objects retrieve the value of the hidden input
field and set the locale in the FacesContext
instance according to which region was selected.
Creating and Using a Custom Converter
A Jakarta Faces converter class converts strings to objects and objects to strings as required. Several standard converters are provided by Jakarta Faces for this purpose. See Using the Standard Converters for more information on these included converters.
As explained in Conversion Model, if the standard converters included with Jakarta Faces cannot perform the data conversion that you need, you can create a custom converter to perform this specialized conversion. This implementation, at a minimum, must define how to convert data both ways between the two views of the data described in Conversion Model.
All custom converters must implement the jakarta.faces.convert.Converter
interface.
This section explains how to implement this interface to perform a custom data conversion.
The Duke’s Bookstore case study uses a custom Converter
implementation, located in jakarta-examples/tutorial/case-studies/dukes-bookstore/src/main/java/jakarta/tutorial/dukesbookstore/converters/CreditCardConverter.java
, to convert the data entered in the Credit Card Number field on the bookcashier.xhtml
page.
It strips blanks and hyphens from the text string and formats it so that a blank space separates every four characters.
Another common use case for a custom converter is in a list for a nonstandard object type.
In the Duke’s Tutoring case study, the Student
and Guardian
entities require a custom converter so that they can be converted to and from a UISelectItems
input component.
Creating a Custom Converter
The CreditCardConverter
custom converter class is created as follows:
@FacesConverter("ccno")
public class CreditCardConverter implements Converter {
...
}
The @FacesConverter
annotation registers the custom converter class as a converter with the name of ccno
with the Jakarta Faces implementation.
Alternatively, you can register the converter with entries in the application configuration resource file, as shown in Registering a Custom Converter.
To define how the data is converted from the presentation view to the model view, the Converter
implementation must implement the getAsObject(FacesContext, UIComponent, String)
method from the Converter
interface.
Here is the implementation of this method from CreditCardConverter
:
@Override
public Object getAsObject(FacesContext context,
UIComponent component, String newValue)
throws ConverterException {
if (newValue.isEmpty()) {
return null;
}
// Since this is only a String to String conversion,
// this conversion does not throw ConverterException.
String convertedValue = newValue.trim();
if ( (convertedValue.contains("-")) || (convertedValue.contains(" "))) {
char[] input = convertedValue.toCharArray();
StringBuilder builder = new StringBuilder(input.length);
for (int i = 0; i < input.length; ++i) {
if ((input[i] == '-') || (input[i] == ' ')) {
} else {
builder.append(input[i]);
}
}
convertedValue = builder.toString();
}
return convertedValue;
}
During the Apply Request Values phase, when the components' decode
methods are processed, the Jakarta Faces implementation looks up the component’s local value in the request and calls the getAsObject
method.
When calling this method, the Jakarta Faces implementation passes in the current FacesContext
instance, the component whose data needs conversion, and the local value as a String
.
The method then writes the local value to a character array, trims the hyphens and blanks, adds the rest of the characters to a String
, and returns the String
.
To define how the data is converted from the model view to the presentation view, the Converter
implementation must implement the getAsString(FacesContext, UIComponent, Object)
method from the Converter
interface.
Here is an implementation of this method:
@Override
public String getAsString(FacesContext context,
UIComponent component, Object value)
throws ConverterException {
String inputVal = null;
if ( value == null ) {
return "";
}
// value must be of a type that can be cast to a String.
try {
inputVal = (String)value;
} catch (ClassCastException ce) {
FacesMessage errMsg = new FacesMessage(CONVERSION_ERROR_MESSAGE_ID);
FacesContext.getCurrentInstance().addMessage(null, errMsg);
throw new ConverterException(errMsg.getSummary());
}
// insert spaces after every four characters for better
// readability if they are not already present.
char[] input = inputVal.toCharArray();
StringBuilder builder = new StringBuilder(input.length + 3);
for (int i = 0; i < input.length; ++i) {
if ((i % 4) == 0 && (i != 0)) {
if ((input[i] != ' ') || (input[i] != '-')){
builder.append(" ");
// if there are any "-"'s convert them to blanks.
} else if (input[i] == '-') {
builder.append(" ");
}
}
builder.append(input[i]);
}
String convertedValue = builder.toString();
return convertedValue;
}
During the Render Response phase, in which the components' encode
methods are called, the Jakarta Faces implementation calls the getAsString
method in order to generate the appropriate output.
When the Jakarta Faces implementation calls this method, it passes in the current FacesContext
, the UIComponent
whose value needs to be converted, and the bean value to be converted.
Because this converter does a String
-to-String
conversion, this method can cast the bean value to a String
.
If the value cannot be converted to a String
, the method throws an exception, passing an error message from the resource bundle that is registered with the application.
Registering Application Messages explains how to register custom error messages with the application.
If the value can be converted to a String
, the method reads the String
to a character array and loops through the array, adding a space after every four characters.
You can also create a custom converter with a @FacesConverter
annotation that specifies the forClass
attribute, as shown in the following example from the Duke’s Tutoring case study:
@FacesConverter(forClass=Guardian.class, value="guardian")
public class GuardianConverter extends EntityConverter implements Converter { ... }
The forClass
attribute registers the converter as the default converter for the Guardian
class.
Therefore, whenever that class is specified by a value
attribute of an input component, the converter is invoked automatically.
A converter class can be a separate Java POJO class, as in the Duke’s Bookstore case study.
If it needs to access objects defined in a managed bean class, however, it can be a subclass of a Jakarta Faces managed bean, as in the address-book
persistence example, in which the converters use an enterprise bean that is injected into the managed bean class.
Using a Custom Converter
To apply the data conversion performed by a custom converter to a particular component’s value, you must do one of the following.
-
Reference the converter from the component tag’s
converter
attribute. -
Nest an
f:converter
tag inside the component’s tag and reference the custom converter from one of thef:converter
tag’s attributes.
If you are using the component tag’s converter
attribute, this attribute must reference the Converter
implementation’s identifier or the fully-qualified class name of the converter.
Creating and Using a Custom Converter explains how to implement a custom converter.
The identifier for the credit card converter class is ccno
, the value specified in the @FacesConverter
annotation:
@FacesConverter("ccno")
public class CreditCardConverter implements Converter {
...
}
Therefore, the CreditCardConverter
instance can be registered on the ccno
component as shown in the following example:
<h:inputText id="ccno"
size="19"
converter="ccno"
value="#{cashierBean.creditCardNumber}"
required="true"
requiredMessage="#{bundle.ReqCreditCard}">
...
</h:inputText>
By setting the converter
attribute of a component’s tag to the converter’s identifier or its class name, you cause that component’s local value to be automatically converted according to the rules specified in the Converter
implementation.
Instead of referencing the converter from the component tag’s converter
attribute, you can reference the converter from an f:converter
tag nested inside the component’s tag.
To reference the custom converter using the f:converter
tag, you do one of the following.
-
Set the
f:converter
tag’sconverterId
attribute to theConverter
implementation’s identifier defined in the@FacesConverter
annotation or in the application configuration resource file. This method is shown inbookcashier.xhtml
:<h:inputText id="ccno" size="19" value="#{cashierBean.creditCardNumber}" required="true" requiredMessage="#{bundle.ReqCreditCard}"> <f:converter converterId="ccno"/> <f:validateRegex pattern="\d{16}|\d{4} \d{4} \d{4} \d{4}|\d{4}-\d{4}-\d{4}-\d{4}"/> </h:inputText>
-
Bind the
Converter
implementation to a managed bean property using thef:converter
tag’sbinding
attribute, as described in Binding Converters, Listeners, and Validators to Managed Bean Properties.
The Jakarta Faces implementation calls the converter’s getAsObject
method to strip spaces and hyphens from the input value.
The getAsString
method is called when the bookcashier.xhtml
page is redisplayed; this happens if the user orders more than $100 worth of books.
In the Duke’s Tutoring case study, each converter is registered as the converter for a particular class.
The converter is automatically invoked whenever that class is specified by a value
attribute of an input component.
In the following example, the itemValue
attribute calls the converter for the Guardian
class:
<h:selectManyListbox id="selectGuardiansMenu"
title="#{bundle['action.add.guardian']}"
value="#{guardianManager.selectedGuardians}"
size="5"
converter="guardian">
<f:selectItems value="#{guardianManager.allGuardians}"
var="selectedGuardian"
itemLabel="#{selectedGuardian.name}"
itemValue="#{selectedGuardian}" />
</h:selectManyListbox>
Creating and Using a Custom Validator
If the standard validators or Bean Validation don’t perform the validation checking you need, you can create a custom validator to validate user input. As explained in Validation Model, there are two ways to implement validation code.
-
Implement a managed bean method that performs the validation.
-
Provide an implementation of the
jakarta.faces.validator.Validator
interface to perform the validation.
Writing a Method to Perform Validation explains how to implement a managed bean method to perform validation.
The rest of this section explains how to implement the Validator
interface.
If you choose to implement the Validator
interface and you want to allow the page author to configure the validator’s attributes from the page, you also must specify a custom tag for registering the validator on a component.
If you prefer to configure the attributes in the Validator
implementation, you can forgo specifying a custom tag and instead let the page author register the validator on a component using the f:validator
tag, as described in Using a Custom Validator.
You can also create a managed bean property that accepts and returns the Validator
implementation you create, as described in Writing Properties Bound to Converters, Listeners, or Validators.
You can use the f:validator
tag’s binding attribute to bind the Validator
implementation to the managed bean property.
Usually, you will want to display an error message when data fails validation. You need to store these error messages in a resource bundle.
After creating the resource bundle, you have two ways to make the messages available to the application.
You can queue the error messages onto the FacesContext
programmatically, or you can register the error messages in the application configuration resource file, as explained in Registering Application Messages.
For example, an e-commerce application might use a general-purpose custom validator called FormatValidator.java
to validate input data against a format pattern that is specified in the custom validator tag.
This validator would be used with a Credit Card Number field on a Facelets page.
Here is the custom validator tag:
<mystore:formatValidator
formatPatterns="9999999999999999|9999 9999 9999 9999|9999-9999-9999-9999"/>
According to this validator, the data entered in the field must be one of the following:
-
A 16-digit number with no spaces
-
A 16-digit number with a space between every four digits
-
A 16-digit number with hyphens between every four digits
The f:validateRegex
tag makes a custom validator unnecessary in this situation.
However, the rest of this section describes how this validator would be implemented and how to specify a custom tag so that the page author could register the validator on a component.
Implementing the Validator Interface
A Validator
implementation must contain a constructor, a set of accessor methods for any attributes on the tag, and a validate
method, which overrides the validate
method of the Validator
interface.
The hypothetical FormatValidator
class also defines accessor methods for setting the formatPatterns
attribute, which specifies the acceptable format patterns for input into the fields.
The setter method calls the parseFormatPatterns
method, which separates the components of the pattern string into a string array, formatPatternsList
.
public String getFormatPatterns() {
return (this.formatPatterns);
}
public void setFormatPatterns(String formatPatterns) {
this.formatPatterns = formatPatterns;
parseFormatPatterns();
}
In addition to defining accessor methods for the attributes, the class overrides the validate
method of the Validator
interface.
This method validates the input and also accesses the custom error messages to be displayed when the String
is invalid.
The validate
method performs the actual validation of the data.
It takes the FacesContext
instance, the component whose data needs to be validated, and the value that needs to be validated.
A validator can validate only data of a component that implements jakarta.faces.component.EditableValueHolder
.
Here is an implementation of the validate
method:
@FacesValidator
public class FormatValidator implements Validator, StateHolder {
...
public void validate(FacesContext context, UIComponent component,
Object toValidate) {
boolean valid = false;
String value = null;
if ((context == null) || (component == null)) {
throw new NullPointerException();
}
if (!(component instanceof UIInput)) {
return;
}
if ( null == formatPatternsList || null == toValidate) {
return;
}
value = toValidate.toString();
// validate the value against the list of valid patterns.
Iterator patternIt = formatPatternsList.iterator();
while (patternIt.hasNext()) {
valid = isFormatValid(
((String)patternIt.next()), value);
if (valid) {
break;
}
}
if ( !valid ) {
FacesMessage errMsg =
new FacesMessage(FORMAT_INVALID_MESSAGE_ID);
FacesContext.getCurrentInstance().addMessage(null, errMsg);
throw new ValidatorException(errMsg);
}
}
}
The @FacesValidator
annotation registers the FormatValidator
class as a validator with the Jakarta Faces implementation.
The validate
method gets the local value of the component and converts it to a String
.
It then iterates over the formatPatternsList
list, which is the list of acceptable patterns that was parsed from the formatPatterns
attribute of the custom validator tag.
While iterating over the list, this method checks the pattern of the component’s local value against the patterns in the list.
If the pattern of the local value does not match any pattern in the list, this method generates an error message.
It then creates a jakarta.faces.application.FacesMessage
and queues it on the FacesContext
for display, using a String
that represents the key in the Properties
file:
public static final String FORMAT_INVALID_MESSAGE_ID =
"FormatInvalid";
}
Finally, the method passes the message to the constructor of jakarta.faces.validator.ValidatorException
.
When the error message is displayed, the format pattern will be substituted for the {0}
in the error message, which, in English, is as follows:
Input must match one of the following patterns: {0}
You may wish to save and restore state for your validator, although state saving is not usually necessary.
To do so, you will need to implement the StateHolder
interface as well as the Validator
interface.
To implement StateHolder
, you would need to implement its four methods: saveState(FacesContext)
, restoreState(FacesContext, Object)
, isTransient
, and setTransient(boolean)
.
See Saving and Restoring State for more information.
Specifying a Custom Tag
If you implemented a Validator
interface rather than implementing a managed bean method that performs the validation, you need to do one of the following.
-
Allow the page author to specify the
Validator
implementation to use with thef:validator
tag. In this case, theValidator
implementation must define its own properties. Using a Custom Validator explains how to use thef:validator
tag. -
Specify a custom tag that provides attributes for configuring the properties of the validator from the page.
To create a custom tag, you need to add the tag to the tag library descriptor for the application, bookstore.taglib.xml
:
<tag>
<tag-name>validator</tag-name>
<validator>
<validator-id>formatValidator</validator-id>
<validator-class>
dukesbookstore.validators.FormatValidator
</validator-class>
</validator>
</tag>
The tag-name
element defines the name of the tag as it must be used in a Facelets page.
The validator-id
element identifies the custom validator.
The validator-class
element wires the custom tag to its implementation class.
Using a Custom Validator explains how to use the custom validator tag on the page.
Using a Custom Validator
To register a custom validator on a component, you must do one of the following.
-
Nest the validator’s custom tag inside the tag of the component whose value you want to be validated.
-
Nest the standard
f:validator
tag within the tag of the component and reference the customValidator
implementation from thef:validator
tag.
Here is a hypothetical custom formatValidator
tag for the Credit Card Number field, nested within the h:inputText
tag:
<h:inputText id="ccno" size="19"
...
required="true">
<mystore:formatValidator
formatPatterns="9999999999999999|9999 9999 9999 9999|9999-9999-9999-9999"/>
</h:inputText>
<h:message styleClass="validationMessage" for="ccno"/>
This tag validates the input of the ccno
field against the patterns defined by the page author in the formatPatterns
attribute.
You can use the same custom validator for any similar component by simply nesting the custom validator tag within the component tag.
If the application developer who created the custom validator prefers to configure the attributes in the Validator
implementation rather than allow the page author to configure the attributes from the page, the developer will not create a custom tag for use with the validator.
In this case, the page author must nest the f:validator
tag inside the tag of the component whose data needs to be validated.
Then the page author needs to do one of the following.
-
Set the
f:validator
tag’svalidatorId
attribute to the ID of the validator that is defined in the application configuration resource file. -
Bind the custom
Validator
implementation to a managed bean property using thef:validator
tag’sbinding
attribute, as described in Binding Converters, Listeners, and Validators to Managed Bean Properties.
The following tag registers a hypothetical validator on a component using an f:validator
tag and references the ID of the validator:
<h:inputText id="name" value="#{CustomerBean.name}"
size="10" ...>
<f:validator validatorId="customValidator" />
...
</h:inputText>
Binding Component Values and Instances to Managed Bean Properties
A component tag can wire its data to a managed bean by one of the following methods:
-
Binding its component’s value to a bean property
-
Binding its component’s instance to a bean property
To bind a component’s value to a managed bean property, a component tag’s value
attribute uses an EL value expression.
To bind a component instance to a bean property, a component tag’s binding
attribute uses a value expression.
When a component instance is bound to a managed bean property, the property holds the component’s local value. Conversely, when a component’s value is bound to a managed bean property, the property holds the value stored in the managed bean. This value is updated with the local value during the Update Model Values phase of the lifecycle. There are advantages to both of these methods.
Binding a component instance to a bean property has the following advantages.
-
The managed bean can programmatically modify component attributes.
-
The managed bean can instantiate components rather than let the page author do so.
Binding a component’s value to a bean property has the following advantages.
-
The page author has more control over the component attributes.
-
The managed bean has no dependencies on the Jakarta Faces API (such as the component classes), allowing for greater separation of the presentation layer from the model layer.
-
The Jakarta Faces implementation can perform conversions on the data based on the type of the bean property without the developer needing to apply a converter.
In most situations, you will bind a component’s value rather than its instance to a bean property.
You’ll need to use a component binding only when you need to change one of the component’s attributes dynamically.
For example, if an application renders a component only under certain conditions, it can set the component’s rendered
property accordingly by accessing the property to which the component is bound.
When referencing the property using the component tag’s value
attribute, you need to use the proper syntax.
For example, suppose a managed bean called MyBean
has this int
property:
protected int currentOption = null;
public int getCurrentOption(){...}
public void setCurrentOption(int option){...}
The value
attribute that references this property must have this value-binding expression:
#{myBean.currentOption}
In addition to binding a component’s value to a bean property, the value
attribute can specify a literal value or can map the component’s data to any primitive (such as int
), structure (such as an array), or collection (such as a list), independent of a JavaBeans component.
Examples of Value-Binding Expressions lists some example value-binding expressions that you can use with the value
attribute.
Value | Expression |
---|---|
A Boolean |
|
A property initialized from a context initialization parameter |
|
A bean property |
|
A value in an array |
|
A value in a collection |
|
A property of an object in an array of objects |
|
The next two sections explain how to use the value
attribute to bind a component’s value to a bean property or other data objects and how to use the binding
attribute to bind a component instance to a bean property.
Binding a Component Value to a Property
To bind a component’s value to a managed bean property, you specify the name of the bean and the property using the value
attribute.
This means that the first part of the EL value expression must match the name of the managed bean up to the first period (.
) and the part of the value expression after the period must match the property of the managed bean.
For example, in the Duke’s Bookstore case study, the h:dataTable
tag in bookcatalog.xhtml
sets the value of the component to the value of the books
property of the BookstoreBean
backing bean, whose name is store
:
<h:dataTable id="books"
value="#{store.books}"
var="book"
headerClass="list-header"
styleClass="list-background"
rowClasses="list-row-even, list-row-odd"
border="1"
summary="#{bundle.BookCatalog}">
The value is obtained by calling the backing bean’s getBooks
method, which in turn calls the BookRequestBean
session bean’s getBooks
method.
Binding a Component Value to an Implicit Object
One external data source that a value
attribute can refer to is an implicit object.
The bookreceipt.xhtml
page of the Duke’s Bookstore case study has a reference to an implicit object:
<h:outputFormat title="thanks"
value="#{bundle.ThankYouParam}">
<f:param value="#{sessionScope.name}"/>
</h:outputFormat>
This tag gets the name of the customer from the session scope and inserts it into the parameterized message at the key ThankYouParam
from the resource bundle.
For example, if the name of the customer is Gwen Canigetit, this tag will render:
Thank you, Gwen Canigetit, for purchasing your books from us.
Retrieving values from other implicit objects is done in a similar way to the example shown in this section.
Implicit Objects lists the implicit objects to which a value attribute can refer.
All of the implicit objects, except for the scope objects, are read-only and therefore should not be used as values for a UIInput
component.
Implicit Object | What It Is |
---|---|
|
A |
|
A |
|
The |
|
A |
|
A |
|
A |
|
A |
|
A |
|
A |
|
A |
|
The root |
Binding a Component Instance to a Bean Property
A component instance can be bound to a bean property using a value expression with the binding
attribute of the component’s tag.
You usually bind a component instance rather than its value to a bean property if the bean must dynamically change the component’s attributes.
Here are two tags from the bookcashier.xhtml
page that bind components to bean properties:
<h:selectBooleanCheckbox id="fanClub"
rendered="false"
binding="#{cashierBean.specialOffer}" />
<h:outputLabel for="fanClub"
rendered="false"
binding="#{cashierBean.specialOfferText}"
value="#{bundle.DukeFanClub}"/>
</h:outputLabel>
The h:selectBooleanCheckbox
tag renders a check box and binds the fanClub
UISelectBoolean
component to the specialOffer
property of the cashier
bean.
The h:outputLabel
tag binds the component representing the check box’s label to the specialOfferText
property of the cashier
bean.
If the application’s locale is English, the h:outputLabel
tag renders
I'd like to join the Duke Fan Club, free with my purchase of over $100
The rendered
attributes of both tags are set to false
to prevent the check box and its label from being rendered.
If the customer makes a large order and clicks the Submit button, the submit
method of CashierBean
sets both components' rendered
properties to true
, causing the check box and its label to be rendered.
These tags use component bindings rather than value bindings because the managed bean must dynamically set the values of the components' rendered
properties.
If the tags were to use value bindings instead of component bindings, the managed bean would not have direct access to the components and would therefore require additional code to access the components from the FacesContext
instance to change the components' rendered
properties.
Writing Properties Bound to Component Instances explains how to write the bean properties bound to the example components.
Binding Converters, Listeners, and Validators to Managed Bean Properties
As described in Adding Components to a Page Using HTML Tag Library Tags, a page author can bind converter, listener, and validator implementations to managed bean properties using the binding
attributes of the tags that are used to register the implementations on components.
This technique has similar advantages to binding component instances to managed bean properties, as described in Binding Component Values and Instances to Managed Bean Properties. In particular, binding a converter, listener, or validator implementation to a managed bean property yields the following benefits.
-
The managed bean can instantiate the implementation instead of allowing the page author to do so.
-
The managed bean can programmatically modify the attributes of the implementation. In the case of a custom implementation, the only other way to modify the attributes outside of the implementation class would be to create a custom tag for it and require the page author to set the attribute values from the page.
Whether you are binding a converter, listener, or validator to a managed bean property, the process is the same for any of the implementations.
-
Nest the converter, listener, or validator tag within an appropriate component tag.
-
Make sure that the managed bean has a property that accepts and returns the converter, listener, or validator implementation class that you want to bind to the property.
-
Reference the managed bean property using a value expression from the
binding
attribute of the converter, listener, or validator tag.
For example, say that you want to bind the standard DateTime
converter to a managed bean property because you want to set the formatting pattern of the user’s input in the managed bean rather than on the Facelets page.
First, the page registers the converter onto the component by nesting the f:convertDateTime
tag within the component tag.
Then, the page references the property with the binding
attribute of the f:convertDateTime
tag:
<h:inputText value="#{loginBean.birthDate}">
<f:convertDateTime binding="#{loginBean.convertDate}" />
</h:inputText>
The convertDate
property would look something like this:
private DateTimeConverter convertDate;
public DateTimeConverter getConvertDate() {
...
return convertDate;
}
public void setConvertDate(DateTimeConverter convertDate) {
convertDate.setPattern("EEEEEEEE, MMM dd, yyyy");
this.convertDate = convertDate;
}
See Writing Properties Bound to Converters, Listeners, or Validators for more information on writing managed bean properties for converter, listener, and validator implementations.