Introducing JSF 2 Client Behaviors
Join the DZone community and get the full member experience.
Join For FreeWe're honored to introduce to this series, which covers new features in JavaServer Faces (JSF) 2.0, guest author Andy Schwartz, a JSR-314 Expert Group (EG) member and architect on the ADF Faces project team at Oracle Corporation. Andy will introduce you to component behaviors, an API which he led, in collaboration with Alex Smirmov (Exadel), Roger Kitain (Sun/Oracle) and Ted Goddard (ICEsoft), to provide a general extension point for adding client-side functionality to existing UI components. This new API quickly received support and input from the entire EG and proved to be a solid foundation on which the declarative Ajax control, introduced in the last part of this series, could be based.
Although Andy represents a different company than the other authors in this series, we come together as colleagues on the JSR-314 EG. The strong partnerships that are built between companies through their participation in JSRs are what make the JCP an asset to the Java EE community and allow the technologies to take such large leaps, in this case JSF.
Author's Note: Many thanks to Dan Allen and Jay Balunas, who played a pivotal role as technical editors of this installment.
Read the other parts in this article series:
Part 1 - JSF 2: Seam's Other Avenue to Standardization
Part 2 - JSF 2 GETs Bookmarkable URLs
Part 3 - Fluent Navigation in JSF 2
Part 4 - Ajax and JSF, Joined At Last
Part 5 - Introducing JSF 2 Client Behaviors
Introduction
In Ajax and JSF, Joined At Last, you discovered how the JSF specification has evolved to provide a standard, out-of-the-box Ajax solution. Armed with this functionality, JSF 2 application developers can optimize user interactions by switching from full-page refreshes to Ajax-based requests that only update specific component subtrees.
Editor's Note: JSF 2.0 is available in AS6 M1, and will be supported by Red Hat in the JBoss Enterprise Application Platform in the near future.
You learned that Ajax requests can be issued programmatically using the jsf.ajax.request() JavaScript API:
<h:commandButton value="Update"
onClick="jsf.ajax.request(this, event,
{render:'updateMe'}; return false"/>
<h:outputText value="#{someValue}" id="updateMe"/>
Or, for those who prefer tags over JavaScript code, Ajax interactions can be defined declaratively (and more concisely) via the <f:ajax> tag:
<h:commandButton value="Update">
<f:ajax render="updateMe"/>
</h:commandButton>
<h:outputText value="#{someValue}" id="updateMe"/>
This article takes a look under the covers of the <f:ajax> tag and introduces another supporting API that has been added to JSF 2: the component client behavior model. We'll see how client behaviors can be used not just for enabling Ajax, but also for attaching arbitrary client-side functionality to JSF 2 UI components.
It's Not Just About Ajax
When designing the declarative Ajax API, the JSR-314 Expert Group (EG) settled on the nested tag-based approach fairly quickly. Other options, such as adding a new set of Ajax-enabled UI components, were considered but rejected due to concerns over API bloat. The tag approach seemed like it would be the most familiar to JSF users, in part due to similarity with the RichFaces <a4j:support> tag, but also due to the parallel found in JSF converters and validators. These cases all share a common goal:
To enhance existing components with new functionality not foreseen by the original component author.
In the case of <f:ajax>, the goal is to insert client-side scripts (i.e. JavaScript code) for triggering Ajax requests from components that were not designed with Ajax capabilities.
While the EG initially focused on the problem of how to attach Ajax behavior to components, there was agreement that a solution which focused exclusively on Ajax would miss an opportunity to introduce a compelling extension point. A client-side behavior contract limited to Ajax would be similar to a validator contract that could only accommodate range validation, but not required field or regular expression validation. On the other hand, a generic solution for associating client-side scripts with UI components opens all sorts of possibilities, including:
- Client-side validation
- DOM and style manipulation
- Animations and visual effects
- Alerts and confirmation dialogs
- Tooltips and hover content
- Keyboard handling
- Deferred (lazy) data fetching
- Integration with 3rd party client libraries
- Client-side logging
We call these client behaviors. Just like with Ajax, you may want to add these features into your JSF application without having to adopt or introduce a whole new component library. Therefore, rather than focus only on Ajax, the scope of the solution was expanded to address the problem of declaratively attaching arbitrary client-side behaviors to UI components.
Two New Contracts
One of the fundamental requirements of the JSF 2 client behavior API is that client behaviors and components should be loosely coupled. Components should not be dependent on any specific client behavior implementation. This means, for example, that component authors should not be required to write Ajax-aware code in order for the component to work with <f:ajax>.
On the flip side, client behaviors should not be dependent on specific component implementations either. (Though they may still depend on a standard or well-known component type). Client behavior authors should be able to implement behaviors that can be attached both to standard components provided by JSF or to custom components provided by a third party.
This loose coupling leads to a clean separation of concerns. Client behaviors are responsible for producing scripts in a component-agnostic manner. Components are responsible for retrieving scripts from client behaviors and inserting these into the rendered markup in a behavior-agnostic manner.
In order to achieve this separation, JSF 2 introduces two new contracts: ClientBehavior and ClientBehaviorHolder. We’ll explore these APIs in the remainder of this section.
ClientBehavior
The ClientBehavior interface defines the mechanism by which client behavior implementations generate scripts. Specifically, client behavior implementations produce JavaScript code that can be inserted into the markup rendered by JSF components. The central method on the ClientBehavior interface is, not surprisingly, getScript():
public String getScript(ClientBehaviorContext context)
The getScript() method returns a string that contains a JavaScript code suitable for inclusion in a DOM event handler. The method’s single argument, the ClientBehaviorContext, provides access to information that may be useful during script generation, such as the FacesContext and the UIComponent to which the behavior is being attached.
A getScript() implementation might simply produce an alert dialog call:
public String getScript(ClientBehaviorContext context) {
return "alert('Hello, World!')";
}
More interesting implementations might take advantage of other client-side APIs, such as the JSF 2 Ajax API:
public String getScript(ClientBehaviorContext context) {
// Look at me sending an Ajax request!
return "jsf.ajax.request(this, event)";
}
Client behavior scripts typically end up being rendered by the associated component as DOM event handlers. For example, when attached to an <h:commandButton> component, an alert behavior script might be rendered in the onclick event handler:
<input type="submit" onclick="alert('Hello, World!')">
However, the decision of how to insert client behavior scripts into the rendered markup is entirely up to the consuming component or its renderer. While the standard components render these scripts as inline event handlers, other components/renderers may use less obtrusive approaches.
ClientBehaviorHolder
The second API, ClientBehaviorHolder, defines the contract by which client behaviors are attached to components. This interface is similar in spirit to EditableValueHolder, which is used to add validators to input components.
Client behavior instances are attached to ClientBehaviorHolders via the ClientBehaviorHolder.addClientBehavior() method:
public void addClientBehavior(String eventName,
ClientBehavior behavior)
One notable difference between EditableValueHolder.addValidator() and ClientBehaviorHolder.addClientBehavior() is the presence of the eventName parameter. Unlike the validation case, where validators are always triggered at the same point in the server-side lifecycle, client behavior scripts may be invoked in response to a variety of client-side events. For example, <h:commandButton> will most commonly fire Ajax requests in response to the user clicking the button. However, an Ajax request may also be sent in response to other user interaction, such as a mouseover event:
<h:commandButton>
<f:ajax event="mouseover"/>
</h:commandButton>
Thus, when attaching a client behavior, the event that triggers the behavior is a vital piece of information.
Once client behaviors have been attached to a component, the component (or renderer), needs access to the set of registered client behaviors in order to retrieve and render the scripts. The ClientBehaviorHolder contract provides access to the attached client behaviors via the getClientBehaviors() method:
public Map<String, List<ClientBehavior>>
getClientBehaviors()
The returned map provides access to the client behaviors that were previously registered via addClientBehavior(), keyed by the event name.
Note that multiple client behaviors can be attached to the same event. To see how this cumulation might be useful, let’s imagine that we have a custom behavior that shows a confirmation dialog that allows the user to cancel an impending action. Such a behavior could be used in conjunction with <f:ajax> to give the end user the option of canceling the Ajax request:
<h:commandButton>
<!--First ask for confirmation -->
<foo:confirm event="click"/>
<!-- If successful, send an Ajax request -->
<f:ajax event="click"/>
</h:commandButton>
When multiple behaviors are registered for the same event, the behavior scripts are chained together and called back in the order in which they appear in the page. If any script in the sequence returns false, the behavior chain is short-circuited and subsequent scripts are ignored.
Logical Client Events
As we've seen, client behavior events often map directly to DOM events (e.g., click, mouseover). As such, these event names are component-specific. For example, the <h:inputText> component fires events when it receives the focus or when its value changes. In contrast, the <h:panelGrid> only fires mouse-related events.
The ClientBehaviorHolder contract identifies the set of component-specific client event names via the getEventNames() method:
public Collection<String> getEventNames()
While these event names typically map to DOM events, this is not a requirement. Components may also expose logical event names that are outside of the DOM event space. The standard JSF HTML components specify two such logical events:
- All command components fire action events
- All editable value holder components fire valueChange events
Why expose these logical events when we already have the DOM-level click and change events? One reason is that the action/valueChange events more closely align with the server-side event abstraction exposed by these components. Command components fire ActionEvents on the server; Editable value holder components fire ValueChangeEvents. Extending this event abstraction to the client provides consistency across tiers.
A more practical reason for exposing these logical events is that this provides component implementations an abstraction away from the often messy and vaguely defined DOM implementations. Additionally, higher-level components may leverage low-level DOM events in ways that may not be apparent to page authors. For example, a toolbar button command component might use DOM click events for purposes other than activating the button. Some clicks might be used to display an associated popup menu. Similarly, a date input component might change its value in response to a user clicking on a link in a popup; there may not be any DOM change event associated with this activity. By providing logical action/valueChange events for command/input components, component authors are free to choose whatever DOM/DOM events suit their implementation needs. Page authors are freed from having to be wary of such implementation details.
Default Events
As we’ve seen above, explicitly specifying an event is not always necessary. The following eventless <f:ajax> usage is valid:
<h:commandButton>
<f:ajax/>
</h:commandButton>
How is the event name determined in this case? The ClientBehaviorHolder contract comes into play again with this method:
public String getDefaultEventName()
When attaching a client behavior, if no event name is specified by the page author, the getDefaultEventName() method is consulted. If a non-null event name is returned, the client behavior is automatically registered using this name. If, however, the page author does not specify an event name and no default name is provided by the ClientBehaviorHolder implementation, the behavior is not registered and an error is reported.
The standard JSF HTML components leverage the default event name mechanism in two cases. All of the standard command components specify action as the default event name, while the standard editable value holder components specify valueChange as their default event. This allows these event names to be omitted, simplifying the page author's job for these very common cases.
A Simple Example
Now that we have had an introduction to the basics of the client behavior API, let's take a look at the steps involved in creating a custom client behavior. We'll revisit our earlier example: a confirmation behavior that prompts the user before performing some action.
The process of creating a custom client behavior requires three steps. Let's review them.
Step 1: Implementing the Behavior
The simplest approach to defining your own behavior is to extend the ClientBehaviorBase base class and implement a single method: getScript(). For our confirmation behavior we have an intentionally trivial getScript() implementation. We simply return a script that calls the JavaScript confirm() API:
public class ConfirmBehavior extends ClientBehaviorBase
{
@Override
public String getScript(
ClientBehaviorContext behaviorContext) {
return "return confirm('Are you sure?')";
}
}
A more interesting getScript() implementation might build a script dynamically based on information specified in the ClientBehaviorContext and client behavior-specific properties.
Step 2: Registering the Behavior
Before we can use our client behavior, the implementation must be registered with JSF. You might expect that this registration requires an entry to faces-config.xml:
<faces-config>
<behavior>
<behavior-id>foo.behavior.Confirm</behavior-id>
<behavior-class>
org.example.foo.behavior.ConfirmBehavior
</behavior-class>
</behavior>
</faces-config>
While such explicit XML-based configuration is indeed supported, fortunately it's not required. Annotation-based configuration in JSF has finally arrived! We can register our client behavior implementation using the @FacesBehavior annotation:
@FacesBehavior("foo.behavior.Confirm")
public class ConfirmBehavior extends ClientBehaviorBase
This achieves the same result without the verbose faces-config.xml entry. The client behavior is registered with JSF under the id specified by annotation value, making it available for use within the application.
In JSF 2, annotation-based configuration can be used for registering not just client behaviors, but for registering other JSF artifacts (e.g., components, converters, validators, renderers, etc.) as well.
Step 3: Defining the Tag Library
We still have one explicit configuration step that must be completed before we can use our custom client behavior. The behavior must be exposed via a Facelets tag library. This configuration is fairly straightforward:
<?xml version='1.0' encoding='UTF-8'?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" version="2.0">
<namespace>http://example.org/foo</namespace>
<tag>
<tag-name>confirm</tag-name>
<behavior>
<behavior-id>foo.behavior.Confirm</behavior-id>
</behavior>
</tag>
</facelet-taglib>
We must specify three pieces of information:
- The namespace of the tag library
- The name of the tag
- The id of the client behavior
The taglib.xml file can be placed either in the web application’s WEB-INF directory or in the META-INF directory of the jar that contains our tag library.
While JSF 2 greatly reduces the need for XML-based configuration, this is one case where explicit configuration is still required. Perhaps this is an area where JSF can be further simplified in the next specification version.
On a positive note, as is the case with components, converters and validators, Facelets provides a default handler for client behaviors. This means that we do not need to go to the trouble of implementing a custom tag handler. Facelets automatically takes care of this bit of legwork for us.
Breaking Away From The Client
Like our sample <foo:confirm> behavior, behaviors often generate scripts that are limited entirely to client-side interaction. However, behaviors may also break outside of the client by issuing requests back into the Faces life cycle running on the server (i.e., postback). The ability for behaviors to reach across tiers in this way greatly expands the set of possible use cases that can be targeted.
As an example of a cross-tier use case, let's consider an auto-suggest behavior that enhances input components with the ability to provide the user with a list of potential completions. The usage for such a behavior might look something like this:
<h:inputText value="#{someValue}">
<foo:suggest suggestions="#{suggestions}"/>
</h:inputText>
The <foo:suggest> behavior binds to a collection of valid suggestions. As the user types into the input control, the behavior added by <foo:suggest> can make calls using jsf.ajax.request() to fetch matching suggestions from the server. These suggestions can then be displayed for selection by the end user.
This solution assumes that some code on the server is available to service the incoming Ajax request and produce a response containing valid suggestions. What are our options for handling these requests?
The <h:inputText> participates in decoding and, as such, could participate in the processing of the suggestion requests. However, due to the loose coupling between components and client behaviors mentioned earlier, <h:inputText> has no intimate knowledge of the <foo:suggest> behavior and is not in a position to produce a suggestion-specific response
Perhaps a PhaseListener could be deployed to sniff out the suggestion request? While this is an option, it requires coordination between the <foo:suggest> behavior and the phase listener, not to mention registration of the phase listener. Ideally, the <foo:suggest> behavior should be self-sufficient. That is, the behavior should be able to service its own requests without requiring the assistance of any external objects or configuration. Surely there must be a simpler way that is more fine-grained and requires less configuration. Fortunately, there is!
The ClientBehavior contract exposes one additional method to specifically address this use case:
public void decode(FacesContext context,
UIComponent component);
The ClientBeahvior.decode() method allows client behavior implementations to participate in request decoding. With this hook into the Faces life cycle, it's possible for a client behavior not only to post back to the server, but also to service the request itself. Our <foo:suggest> behavior can leverage this method to service its own requests in an autonomous manner.
Extending Event Handling Across Tiers
The ability for behaviors to implement cross-tier processing also comes in handy for calling out to application-specific logic. In the typical command Ajax case, application logic can be hosted in an action listener.
<h:commandButton actionListener="#{someBean.doAction}">
<f:ajax/>
</h:commandButton>
The action listener is invoked regardless of whether the action event is sent via an Ajax request or a traditional form post. This works well for cases where an ActionEvent is delivered. However, we might also issue an Ajax request in response to client events that do not trigger actions, such as a mouseover event:
<h:commandButton actionListener="#{someBean.doAction}">
<!-- Fetch tooltip data over Ajax -->
<f:ajax event="mouseover"/>
</h:commandButton>
Fetching the tooltip data in response to a mouseover client event does not cause an ActionEvent to be delivered and as such does not invoke the command component’s action listener. We need some other way to invoke application logic in such scenarios.
<f:ajax> leverages the ClientBehavior.decode() method to provide a solution. The application can register an event-specific server-side listener using <f:ajax>'s listener attribute:
<f:ajax event="mouseover"
listener="#{someBean.doMouseOover}"/>
During decoding, the<f:ajax> behavior detects requests issued by its own client-side script and calls back the application-specified listener if present.
This decode mechanism allows client-side event handling to be extended across tiers in a generic manner. Any client-side event can now be propagated back to the server where it can be processed both by the client behavior implementation as well as by application-specified logic.
Conclusion
Hopefully by now you are ready to not only start using <f:ajax>, but to create your own custom client behavior implementations as well. One of the goals of providing an extensible API is to encourage JSF users to leverage this for their own needs. While we only have a single standard behavior (<f:ajax>) today, we expect to see many third party behavior implementations in the days to come. Some of these behaviors may be candidates for inclusion in future versions of the JSF specification – perhaps even yours!
In this article, you learned how client behaviors can be added to existing UI components. There's one behavior you don't have to write a behavior component for at all. The Bean Validation integration in JSF 2 can automatically register validators on input components by applying the Bean Validation constraints defined on the mapped bean property. You'll learn about this tremendously convenient integration in the next installment in this series.
Opinions expressed by DZone contributors are their own.
Trending
-
How To Use Pandas and Matplotlib To Perform EDA In Python
-
Working on an Unfamiliar Codebase
-
How To Scan and Validate Image Uploads in Java
-
AI Technology Is Drastically Disrupting the Background Screening Industry
Comments