Dan is an open source advocate, community catalyst, author and speaker. He's currently pursuing these interests as a Principal Software Engineer at Red Hat. In that role, he serves as a JBoss Community liaison, contributes to several JBoss Community projects, including Arquillian, ShrinkWrap, Seam 3 / DeltaSpike and JBoss Forge, and participates in the JCP on behalf of Red Hat. Dan is the author Seam in Action (Manning, 2008), writes for IBM developerWorks, NFJS magazine and JAXenter and is an internationally recognized speaker. He's presented at major software conference series including JavaOne, Devoxx, NFJS, JAX and Jazoon. After a long conference day, you'll likely find Dan enjoying tech talk with fellow community members over a Belgian Trappist beer. Dan has posted 5 posts at DZone. You can read more from them at their website. View Full User Profile

Introducing JSF 2 Client Behaviors

02.22.2010
| 84772 views |
  • submit to reddit

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

  1. All command components fire action events
  2. 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.

 

Published at DZone with permission of its author, Dan Allen.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Sebastian Herold replied on Tue, 2010/03/09 - 9:58am

I tryed to code your suggestion example but to assign my JS-code to multiple events, I had more than one occurence of the tag:
<foo:suggest event='blur' suggester='#{suggester}' />
<foo:suggest event='keyup' suggester='#{suggester}' />
....

Is it possible to assign script code to multiple events without more than one tag and without to set the event-attribute?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.