Статьи

Представляем JSF 2 клиентское поведение

Для нас большая честь представить эту серию, которая охватывает новые функции JavaServer Faces (JSF) 2.0, приглашенный автор Энди Шварц, член экспертной группы JSR-314 (EG) и архитектор в команде проекта ADF Faces в корпорации Oracle. Энди познакомит вас с поведением компонентов, API, которым он руководил в сотрудничестве с Алексом Смирмовым (Exadel), Роджером Китэйном (Sun / Oracle) и Тедом Годдардом (ICEsoft), чтобы предоставить общую точку расширения для добавления функциональности на стороне клиента к существующие компоненты пользовательского интерфейса. Этот новый API быстро получил поддержку и вклад от всего EG и оказался прочной основой, на которой можно было основывать декларативный элемент управления Ajax, представленный в последней части этой серии .

Хотя Энди представляет компанию, отличную от других авторов этой серии, мы собрались вместе как коллеги по JSR-314 EG. Прочные партнерские отношения, которые выстраиваются между компаниями благодаря их участию в JSR, делают JCP активом сообщества Java EE и позволяют технологиям совершать такие большие скачки, в данном случае JSF.

Примечание автора: большое спасибо Дэну Аллену и Джею Балунасу, которые сыграли ключевую роль в качестве технических редакторов этой статьи.

Прочитайте другие части этой серии статей:

Часть 1 —
JSF 2: Другой
путь к стандартизации в Seam
Часть 2 —
JSF 2 GETs Закладочные URL-адреса 

Часть 3 —
Свободная навигация в JSF 2

Часть 4 —
Ajax и JSF, присоединились наконец

Часть 5 —
Введение Поведение клиента JSF 2

 

Вступление

В Ajax и JSF, наконец- то присоединившихся , вы узнали, как развивается спецификация JSF для предоставления стандартного готового решения Ajax.   Вооружившись этой функциональностью, разработчики приложений JSF 2 могут оптимизировать взаимодействие с пользователем, переключаясь с полностраничных обновлений на запросы на основе Ajax, которые обновляют только отдельные поддеревья компонентов.

Примечание редактора: JSF 2.0 доступен в AS6 M1 и будет поддерживаться Red Hat в JBoss Enterprise Application Platform в ближайшем будущем.

 Вы узнали, что запросы Ajax могут быть выполнены программно с помощью JavaScript API jsf.ajax.request () :

  <h:commandButton value="Update"
onClick="jsf.ajax.request(this, event,
{render:'updateMe'}; return false"/>
<h:outputText value="#{someValue}" id="updateMe"/>

 

Или для тех, кто предпочитает тэги, а не код JavaScript, Ajax-взаимодействия могут быть определены декларативно (и более кратко) с помощью тега <f: ajax>:

  <h:commandButton value="Update">
<f:ajax render="updateMe"/>
</h:commandButton>
<h:outputText value="#{someValue}" id="updateMe"/>

 

В этой статье мы рассмотрим тег <<: ajax> и представим еще один поддерживающий API, который был добавлен в JSF 2: компонентную модель поведения клиента.   Мы увидим, как поведение клиента можно использовать не только для включения Ajax, но и для присоединения произвольных функций на стороне клиента к компонентам пользовательского интерфейса JSF 2.

 

Это не только Ajax

При разработке декларативного API Ajax экспертная группа JSR-314 довольно быстро остановилась на подходе на основе вложенных тегов. Другие варианты, такие как добавление нового набора компонентов пользовательского интерфейса с поддержкой Ajax, рассматривались, но были отклонены из-за опасений по поводу раздувания API. Подход с использованием тегов казался наиболее знакомым пользователям JSF, отчасти из-за сходства с тегом RichFaces <a4j: support>, а также из-за параллели, обнаруженной в преобразователях и валидаторах JSF. Все эти случаи имеют общую цель:     

Расширить существующие компоненты новыми функциональными возможностями, не предусмотренными автором исходного компонента .

В случае <f: ajax> цель состоит в том, чтобы вставить клиентские сценарии (например, код JavaScript) для запуска запросов Ajax от компонентов, которые не были разработаны с возможностями Ajax.

В то время как EG изначально фокусировался на проблеме того, как привязать поведение Ajax к компонентам, было достигнуто согласие, что решение, ориентированное исключительно на Ajax, упустит возможность представить убедительную точку расширения.   Контракт на стороне клиента, ограниченный Ajax, будет аналогичен контракту валидатора, который может включать только проверку диапазона, но не обязательную проверку поля или регулярного выражения.   С другой стороны, универсальное решение для связи клиентских сценариев с компонентами пользовательского интерфейса открывает все возможности, в том числе: 

 

  • Проверка на стороне клиента

  • DOM и стиль манипуляции

  • Анимации и визуальные эффекты

  • Оповещения и диалоги подтверждения

  • Подсказки и всплывающее содержание

  • Управление клавиатурой

  • Отложенная (отложенная) выборка данных

  • Интеграция со сторонними клиентскими библиотеками

  • Регистрация на стороне клиента

 

Мы называем это поведением клиента. Как и в случае с Ajax, вы можете захотеть добавить эти функции в ваше JSF-приложение без необходимости принимать или вводить новую библиотеку компонентов. Поэтому вместо того, чтобы фокусироваться только на Ajax, сфера решения была расширена для решения проблемы декларативного присоединения произвольных поведений на стороне клиента к компонентам пользовательского интерфейса.

 

Два новых контракта

 Одним из фундаментальных требований API поведения клиента JSF 2 является то, что поведение клиента и его компоненты должны быть слабо связаны. Компоненты не должны зависеть от какой-либо конкретной реализации поведения клиента.   Это означает, например, что от авторов компонента не требуется писать код с поддержкой Ajax, чтобы компонент работал с <f: ajax>.

 С другой стороны, поведение клиента также не должно зависеть от конкретных реализаций компонентов.   (Хотя они все еще могут зависеть от стандартного или известного типа компонента). Авторы поведения клиента должны иметь возможность реализовывать поведение, которое может быть присоединено как к стандартным компонентам, предоставляемым JSF, так и к пользовательским компонентам, предоставляемым третьей стороной.

 Эта слабая связь приводит к чистому разделению проблем.   Поведение клиента отвечает за создание сценариев независимо от компонентов.   Компоненты отвечают за извлечение сценариев из поведения клиента и вставку их в визуализированную разметку без учета поведения.

 Для достижения этого разделения JSF 2 вводит два новых контракта: ClientBehavior и ClientBehaviorHolder .   Мы рассмотрим эти API в оставшейся части этого раздела.

 

ClientBehavior

 Интерфейс ClientBehavior определяет механизм, с помощью которого реализации поведения клиента генерируют сценарии.   В частности , реализации поведения клиента создают код JavaScript, который можно вставить в разметку, представленную компонентами JSF.   Неудивительно, что центральным методом в интерфейсе ClientBehavior является getScript () :

  public String getScript(ClientBehaviorContext context)

 

Метод getScript () возвращает строку, содержащую код JavaScript, подходящий для включения в обработчик событий DOM.   Единственный аргумент метода, ClientBehaviorContext , обеспечивает доступ к информации, которая может быть полезна при генерации скрипта, такой как FacesContext и UIComponent, к которому привязано поведение.

GetScript () реализация может просто произвести предупредительный диалог вызова:

  public String getScript(ClientBehaviorContext context) {
return "alert('Hello, World!')";
}

 

Более интересные реализации могут использовать преимущества других API на стороне клиента, таких как JSF 2 Ajax API:

  public String getScript(ClientBehaviorContext context) {
// Look at me sending an Ajax request!
return "jsf.ajax.request(this, event)";
}

 

Сценарии поведения клиента обычно заканчиваются рендерингом ассоциированным компонентом как обработчики событий DOM.   Например, при подключении к компоненту <h: commandButton> сценарий поведения оповещения может отображаться в обработчике события onclick:

  <input type="submit" onclick="alert('Hello, World!')">

 

Однако решение о том, как вставить сценарии поведения клиента в отображаемую разметку, полностью зависит от компонента-потребителя или его средства визуализации.   Хотя стандартные компоненты отображают эти сценарии как встроенные обработчики событий, другие компоненты / средства визуализации могут использовать менее навязчивые подходы.

 

ClientBehaviorHolder

Второй API, ClientBehaviorHolder , определяет контракт, с помощью которого клиентские поведения связаны с компонентами.   Этот интерфейс по духу похож на EditableValueHolder , который используется для добавления валидаторов к компонентам ввода. 

Экземпляры поведения клиента присоединяются к ClientBehaviorHolders с помощью метода ClientBehaviorHolder.addClientBehavior () :

  public void addClientBehavior(String eventName,
ClientBehavior behavior)

 

Одно заметное отличие между EditableValueHolder.addValidator () и ClientBehaviorHolder.addClientBehavior () заключается в наличии параметра eventName .   В отличие от случая проверки, когда средства проверки всегда запускаются в одной и той же точке жизненного цикла на стороне сервера, сценарии поведения клиента могут вызываться в ответ на различные события на стороне клиента.   Например, <h: commandButton> чаще всего запускает Ajax-запросы в ответ на нажатие пользователем кнопки.   Однако запрос Ajax также может быть отправлен в ответ на другое взаимодействие с пользователем, такое как событие наведения мыши :

  <h:commandButton>
<f:ajax event="mouseover"/>
</h:commandButton>

 

Таким образом, при присоединении поведения клиента событие, которое запускает поведение, является важной частью информации.

После того, как клиентские поведения были присоединены к компоненту, компоненту (или средству визуализации) необходим доступ к набору зарегистрированных клиентских поведений для получения и визуализации сценариев. ClientBehaviorHolder договор предусматривает доступ к прилагаемому клиенту поведению через getClientBehaviors () метод: 

  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.

 

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:


  1. The namespace of the tag library

  2. The name of the tag

  3. 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.