FacesTales

Das Behavior API – Teil 2

Lars Röwekamp und Matthias Weßendorf

Die letzte Faces-Tales-Kolumne gab einen kurzen Einstieg in das neue Behavior API von JSF 2.0. Behavior sind keine JSF-Komponenten im klassischen Sinne, sondern Attached Objects, mit denen funktionales JavaScript in eine Komponente injiziert werden kann. Ein Vorgehen, dass JSF-Programmieren bereits durch das Konzept der Validator und Converter bekannt ist.

Klare Verhältnisse

Die Hilfsklasse ClientBehaviorBase wurde bereits im ersten Teil vorgestellt und es wurde gezeigt, wie innerhalb der überschriebenen getScript()-Methode das gewünschte JavaScript erzeugt werden kann. Eine Behavior-Implementierung ist – ähnlich wie eine JSF-Komponente – der Kernbestandteil eines „Komponenten API“. Das Rendering dagegen ist lediglich ein Detail der Implementierung. Als logische Konsequenz sollte die JavaScript-Erzeugung innerhalb einer separaten ClientBehaviorRenderer-Klasse stattfinden, da der getScript()-Aufruf beim Rendering des ClientBehaviorHolders, also der JSF-Komponente, der das Behavior beigefügt wurde, erfolgt. Dazu muss die getRendererType()-Methode des Behaviors überschrieben werden (Listing 1).

@FacesBehavior("com.entwickler.ShowPopup")
public class ShowPopupBehavior extends ClientBehaviorBase
{
 @Override
 public String getRendererType()
 {
   return "com.entwickler.ShowPopup";
 }

 // Mehr API Bestandteile des eigenen Behaviors
}

Die Implementierung des passenden ClientBehaviorRenderer sieht wie in Listing 2 aus.

@FacesBehaviorRenderer(rendererType = "com.entwickler.ShowPopup"
@ResourceDependency(name="myPopup.js", target="head")
public class ShowPopupBehaviorRenderer extends ClientBehaviorRenderer
{
  @Override
  public String getScript(ClientBehaviorContext behaviorContext,
     ClientBehavior behavior)
  {
    if ("mouseover".equals(behaviorContext.getEventName()))
      return "showMyPopup(...);";
   ....
 }
}

Die notwendige JavaScript-Datei (myPopup.js) wird über das Resource API von JSF2 (@ResourceDependency) mit dem ClientBehaviorRenderer in Verbindung gebracht. Innerhalb der getScript()-Methode wird nun auf die unterschiedlichen DOM-Events eingegangen. Für das mouseover-Event wird der String showMyPopup(…); und somit ein Verweis auf eine JS-Methode der oben bereits erwähnten ResourceDependency myPopUp.js in das HTML der Seite gerendert. Um das eigene Behavior letztendlich mit den gewünschten DOM-Events zu verbinden, benötigt man noch einen Facelets-TagHandler. Für die Programmierung eigener Behaviors stellt das JSF 2.0 API eine entsprechende Hilfsklasse bereit (Listing 3).

public class ShowPopupBehaviorHandler extends BehaviorHandler
{
 public ShowPopupBehaviorHandler(BehaviorConfig config)
 { super(config);}

 @Override
 public void apply(FaceletContext ctx, UIComponent parent)
 {
   ClientBehaviorHolder holder = _getClientBehaviorHolder(parent);
   // Erzeugen des ShowPopupBehavior
   FacesContext context = ctx.getFacesContext();
   Application app = context.getApplication();
   String behaviorId = getBehaviorId();
   Behavior behavior = app.createBehavior(behaviorId);

   setAttributes(ctx, behavior);

   // Event-Registrierung
   ClientBehavior clientBehavior = (ClientBehavior)behavior;
   holder.addClientBehavior(ShowPopup.MOUSE_OUT, clientBehavior);
   ...
 }
 ...

Innerhalb der überschriebenen apply()-Methode wird eine Instanz des Behaviors erzeugt und die gewünschten (DOM-)Events werden mit der Komponente (ClientBehaviorHolder) bekannt gemacht. Bei der Implementierung eines solchen BehaviorHandlers sollte eine Prüfung vorgenommen werden, ob die einbettende Komponente überhaupt das ClientBehaviorHolder-Interface implementiert. Zusätzlich sollte unbedingt geprüft werden, ob die gewünschten (DOM-)Events auch vom der ClientBehaviorHolder unterstützt werden. Beispiele für vollständige Behavior-Implementierungen sind innerhalb des Apache-MyFaces-Trinidad-Projekts sowie auf Google-Code verfügbar.

Hinweise

Das Behavior API erinnert sehr stark an das der klassischen JSF-Komponenten. Eine vollständige Behavior-Implementierung besteht dabei aus folgenden Bestandteilen: der Behavior-Klasse, einem speziellen Renderer und einer Tag-Klasse. Auch das Verhalten der ClientBehaviorRenderer ähnelt dem der normalen Renderer. Bei ihrer Programmierung muss auf die Nebenläufigkeit geachtet werden, da sie lediglich einmal pro Request erzeugt werden. Übrigens funktioniert auch das Verarbeiten vom HTTP-Request-Parameter mit einem Behavior. Dazu muss die ClientBehaviorRenderer-Klasse lediglich die decode()-Methode überschreiben. Eine Komponente kann so mithilfe eines sehr speziellen Behaviors beispielsweise Ajax-Daten an das FacesServlet schicken. Der ClientBehaviorRenderer ist in diesem Fall für die Erstellung des Response zuständig:

public void decode(FacesContext context, UIComponent component, 
                                            ClientBehavior behavior)
{
  ExternalContext external = context.getExternalContext();
  external.setResponseContentType("text/xml");

  PartialViewContext partial = context.getPartialViewContext();
  PartialResponseWriter writer = partial.getPartialResponseWriter();

  writer.startDocument();
  ...
  writer.endDocument();
  writer.flush();
  context.responseComplete();
}

Achtung: Wenn ein Behavior einen Request an das FacesServlet schickt, muss die getHints()-Methode das ClientBehaviorHint.SUBMITTING enthalten. Das bewirkt, dass das JSF-Framework nicht zusätzlich das vollständige <h:form> an das FacesServlet abschickt.

Fazit

Die Programmierung von komplexeren Behaviors ist nicht trivial. Diesem Nachteil steht allerdings ein erheblicher Mehrwert gegenüber: Das Behavior API bietet eine solide Möglichkeit, funktionales JavaScript in die Anwendung zu integrieren. In der Regel wird das Behavior API eher von Komponentenherstellern genutzt werden. Trotzdem wäre eine Vereinfachung des API für zukünftige JSF-Versionen sinnvoll. Gute Nachrichten gibt es schon heute: Das Apache-MyFaces-Trinidad-Projekt plant für seine Maven-Plug-ins Unterstützung bei der Generierung von eigenen BehaviorHandler-Klassen ein. Wer sich mit der Konzeption und Umsetzung von Behaviors beschäftigt, betritt derzeit noch Neuland. Detaillierte Dokumentation oder Implementation Guides sucht man (noch) vergeblich. Das wird sich aber sicherlich aufgrund der völlig neuen Möglichkeiten, die sich dank Behaviors der JSF-Community bieten, in naher Zukunft ändern.

Lars Röwekamp ist Geschäftsführer der OpenKnowledge GmbH und berät seit mehr als 10 Jahren Kunden in internationalen Projekten rund um das Thema „Enterprise Computing“ (Twitter: @mobileLarson).

Matthias Weßendorf arbeitet für Oracle an einer Server-Side-Push-Lösung für ADF Faces. Er ist PMC Chair von Apache MyFaces. Matthias bloggt regelmäßig auf http://matthiaswessendorf.wordpress.com (Twitter: @mwessendorf).

Geschrieben von
Lars Röwekamp und Matthias Weßendorf
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.