Verändern des Rendering-Verhaltens einer Komponente durch Component-10mmBinding
 
  Tag-Handler für die Beispielkomponente
 
  Tag-Handler für c:if
 
  Struktur der eigenen Komponenten-bibliothek
 
  Seitenleiste in MyGourmet mit panelBox
 
  Ressourcenannotationen auf der Rendererklasse
 
  Ressourcen in einer Kompositkomponente
 
  Ressource der Kompositkomponente panelBox
 
  Rendern eines Labels mit Stern für Pflichtfelder
 
  Registrierung einer Rendererklasse mittels Annotation
 
  Registrierung einer Rendererklasse
 
  Registrierung einer Komponentenklasse mittels Annotation
 
  Registrierung einer Komponentenklasse
 
  MyGourmet 13: Setzen des Service in ProviderBean
 
  MyGourmet 13: Ressourcen der Bibliothek mygourmet
 
  MyGourmet 13: ProviderService
 
  MyGourmet 13: Implementierung des Service
 
  Method-Expressions als Attribute
 
  Mehrere Action-Attribute mit JSF 2.1
 
  Mehrere Action-Attribute mit JSF 2.0
 
  Konvertieren des Werts im Beispielrenderer
 
  Konfiguration eines Renderers
 
  Konfiguration der Tag-Bibliothek für die Komponenten-bibliothek
 
  Kompositkomponente simpleInputim Einsatz
 
  Kompositkomponente simpleInputim Einsatz
 
  Kompositkomponente simpleInput
 
  Kompositkomponente panelBox im Einsatz
 
  Kompositkomponente panelBox
 
  Kompositkomponente inputSpinner
 
  Kompositkomponente dataTable
 
  Kompositkomponente collapsiblePanelmit benutzerdefinierter Wurzelkomponente
 
  Kompositkomponente collapsiblePanelim Einsatz
 
  Kompositkomponente collapsiblePanel
 
  Komposition klassischer Komponenten mit System-Events
 
  Komponente CollapsiblePanel
 
  JavaScript für inputSpinner
 
  Gerenderte Ausgabe von panelBox in MyGourmet 13
 
  Gerenderte Ausgabe von inputSpinner
 
  Gerenderte Ausgabe von dataTable
 
  Gerenderte Ausgabe von collapsiblePanel
 
  Finale Version der Kompositkomponente panelBox
 
  faces-config.xml für die Komponentenbibliothek
 
  Einbinden einer Kompositkomponente
 
  Direktes Rendern von Ressourcen
 
  Die Methode encodeBegin() des Beispielrenderers
 
  Die Klasse InputSpinner
 
  Definition des Tags mittels Annotation
 
  Definition des Tags der Beispielkomponente mit Tag-Handler
 
  Definition des Tags der Beispielkomponente
 
  Decodieren eines Werts
 
  Bibliotheksname this
 
  Auslesen des Werts einer Komponente beim Rendern

6 Die eigene JSF-Komponente

Seinem Ruf als Komponentenframework wird JavaServer Faces mehr als gerecht: Komponenten sind ein essenzieller Bestandteil von JSF und bilden einen zentralen Erweiterungspunkt. Es gibt wohl bei keinem anderen Webframework so viele Möglichkeiten zur Erweiterung und zum Erstellen von eigenen Komponenten, die harmonisch mit dem Framework interagieren. Die Standardkomponenten und diverse Erweiterungen und Komponentenbibliotheken aus dem JSF-Umfeld decken bereits ein beachtliches Funktionsspektrum ab. Die meisten Entwickler werden aber früher oder später auf Anwendungsfälle stoßen, die damit nicht realisierbar sind. Spätestens dann ist eine eigene Komponente sinnvoll.
Vor Version 2.0 von JSF war die Komponentenentwicklung jedoch relativ aufwendig. Es galt immer abzuwägen, ob sich dieser Schritt im konkreten Fall auszahlt oder sich das Problem nicht auch anderweitig lösen lässt. JSF machte in Version 2.0 in diesem Bereich einen großen Schritt auf die Entwickler zu. Mit den neuen Kompositkomponenten (Composite-Components) existiert eine wirklich sehr einfache Möglichkeit, Komponenten deklarativ zu erstellen - ohne eine Zeile Java-Code oder XML-Konfiguration zu schreiben. Wie das funktioniert, zeigen wir Ihnen in Abschnitt [Sektion:  Kompositkomponenten] .
So wichtig die Kompositkomponenten auch sind, sie können nicht jedes Problem lösen. Speziell komplexere Aufgaben lassen sich weiterhin nur mit den höchstflexiblen klassischen Komponenten realisieren. Dass aber auch deren Erstellung kein Hexenwerk ist, zeigen wir Ihnen in Abschnitt [Sektion:  Klassische Komponenten] . Mit JSF ab Version 2.0 und Facelets ist es im Vergleich sogar noch etwas einfacher geworden.
In Abschnitt [Sektion:  Kompositkomponenten und klassische Komponenten kombinieren] gehen wir noch einen Schritt weiter und kombinieren Kompositkomponenten mit klassischen Komponenten. Mit diesem Ansatz lassen sich Kompositkomponenten bei Bedarf sehr einfach mit Java-Code erweitern. Sie werden sehen, dass die beiden Konzepte perfekt miteinander harmonieren und die Entwicklung von eigenen Komponenten damit noch flexibler wird.
In Abschnitt [Sektion:  Alternativen zur eigenen Komponente] zeigen wir Ihnen, wie Sie einzelne Teile einer existierenden Komponente ersetzen: Es muss nicht immer eine komplette Komponente sein. Das Beispiel MyGourmet 13 fasst alle im Laufe des Kapitels gemachten Änderungen zusammen und wird in Abschnitt [Sektion:  MyGourmet 13: Komponenten und Services] behandelt.
Sie wollen eine eigene Komponentenbibliothek mit all Ihren Komponenten erstellen? Kein Problem, Abschnitt [Sektion:  Die eigene Komponentenbibliothek] zeigt, wie das funktioniert. In Abschnitt [Sektion:  MyGourmet 13 mit Komponentenbibliothek] finden Sie nochmals MyGourmet 13 - diesmal allerdings mit Komponentenbibliothek.

6.1 Kompositkomponenten

Kompositkomponenten waren aus unserer Sicht eines der wichtigsten neuen Features von JSF 2.0. Entwickler erhalten durch die Verbindung von Facelets und Ressourcen die Möglichkeit, Komponenten aus beinahe beliebigen Seitenfragmenten aufzubauen - daher auch der Name. Eine Kompositkomponente ist im Grunde nichts anderes als ein XHTML-Dokument, das in einer Ressourcenbibliothek abgelegt ist und die Komponente deklariert.
Nach einer kurzen Einführung anhand eines Beispiels in Abschnitt [Sektion:  Eine erste Kompositkomponente] werden wir in den Abschnitten [Sektion:  Der Bereich cc:interface] und [Sektion:  Der Bereich cc:implementation] etwas genauer auf die Deklaration von Kompositkomponenten und die einzelnen Tags der Composite-Tag-Bibliothek eingehen. Anschließend zeigt Abschnitt [Sektion:  Ressourcen in Kompositkomponenten] die Kombination von Kompositkomponenten und Ressourcen. In den Abschnitten [Sektion:  Die Komponente mc:panelBox] bis [Sektion:  Die Komponente mc:inputSpinner] finden Sie einige praxisorientierte Beispiele und Abschnitt [Sektion:  Fallstricke in der Praxis] zeigt abschließend noch einige Fallstricke beim Einsatz von Kompositkomponenten.

6.1.1 Eine erste Kompositkomponente

Die Definition des Namensraums und des Tags der Komponente ergibt sich per Konvention aus der Ressourcenbibliothek und dem Namen des XHTML-Dokuments. Als erstes Beispiel wandeln wir die Box in der Seitenleiste in eine Kompositkomponente mit dem Namen panelBox um. Da alle im Laufe dieses Abschnitts erstellten Komponenten in der Bibliothek mygourmet landen, legen wir zuerst dieses Verzeichnis unter /resources an. Darin erstellen wir dann die Deklaration der Komponente mit dem Namen panelBox.xhtml . Abbildung Ressource der Kompositkomponente panelBox zeigt den Verzeichnisbaum. Ausführlichere Informationen zum Thema Ressourcen finden Sie in Kapitel Kapitel:  Verwaltung von Ressourcen .
Abbildung:Ressource der Kompositkomponente panelBox
Sobald panelBox.xhtml in der Bibliothek mygourmet existiert, ist die Komponente einsatzfähig. Das Einbinden erfolgt wie bei allen Komponenten über den Namensraum und das Tag. Per Konvention leitet sich der Namensraum von Kompositkomponenten aus dem Präfix http://xmlns.jcp.org/jsf/composite/ gefolgt vom Bibliotheksnamen - in unserem Fall mygourmet - ab. Das Tag erhält seinen Namen von der Deklaration und lautet panelBox . Listing Einbinden einer Kompositkomponente zeigt, wie die Komponente in einer Seitendeklaration zum Einsatz kommt. Das gewählte Präfix mc ist wie immer beliebig, bietet sich aber als Abkürzung von MyGourmet-Components an.
<html xmlns:mc="http://xmlns.jcp.org/jsf/composite/mygourmet">
  ...
  <mc:panelBox title="Panel-Header">
    <h:outputText value="Ein Text"/>
  </mc:panelBox>
  ...
</html>
Da wir die Komponente bereits einbinden können, wird es Zeit, einen Blick auf die Deklaration zu werfen. Listing Kompositkomponente panelBox zeigt eine erste Version von panelBox.xhtml , deren Inhalt stark an das zuvor eingesetzte Template sideBox.xhtml (siehe Abschnitt Sektion:  Mehrere Templates pro Seite ) angelehnt ist.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<cc:interface>
  <cc:attribute name="title"/>
</cc:interface>
<cc:implementation>
  <div class="side_box">
    <p class="header">#{cc.attrs.title}</p>
    <cc:insertChildren/>
  </div>
</cc:implementation>
</ui:composition>
JSF fasst im Namensraum http://xmlns.jcp.org/jsf/composite alle zur Deklaration einer Kompositkomponente notwendigen Tags zusammen. Wenn Sie Listing Kompositkomponente panelBox näher betrachten, werden Sie bemerken, dass es sich wie schon beim Templating um ein XHTML-Dokument mit dem Wurzelelement ui:composition handelt. Für die Deklaration der Komponente sind aber nur die mit cc:interface und cc:implementation umschlossenen Bereiche relevant. Facelets ignoriert den Rest.
Der Bereich innerhalb von cc:interface definiert die Schnittstelle der Komponente nach außen. Bei unserer Box fällt dieser Bereich relativ klein aus und umfasst nur ein Attribut mit dem Namen title . Der Wert dieses Attributs wird über das Tag der Komponente gesetzt, wie bereits Listing Einbinden einer Kompositkomponente gezeigt hat. Wie dieses Attribut innerhalb der Komponente verwendet wird, zeigen wir gleich.
Im Bereich cc:implementation folgt die Implementierung der Komponente, die aus einem beliebigen Mix aus JSF-Tags, HTML-Tags und Tags der Composite-Tag-Bibliothek zusammengesetzt sein kann. Unsere Box besteht aus einem div -Element, in dem als erster Absatz der Titel ausgegeben wird. Da der Titel ein Attribut der Komponente ist, müssen wir irgendwie auf dessen Wert zugreifen. Dazu führt JSF 2.0 das implizite Objekt cc als Referenz auf die aktuelle Kompositkomponente ein. Der Zugriff auf das Attribut erfolgt mit der Value-Expression #{cc.attrs.title} , wobei die Eigenschaft attrs eine Map aller Attribute zurückliefert. Das Tag cc:insertChildren veranlasst JSF dazu, sämtliche Inhalte einzufügen, die beim Einsatz der Komponente innerhalb des Tags angegeben werden. Im Beispiel aus Listing Einbinden einer Kompositkomponente ist das die h:outputText -Komponente mit dem Wert Ein Text .
Bleibt noch zu klären, wie JSF mit Kompositkomponenten in einer Seitendeklaration umgeht. Listing Seitenleiste in MyGourmet mit panelBox zeigt, wie der Inhalt der Seitenleiste aus MyGourmet mit der Komponente panelBox aussieht.
<mc:panelBox id="menu" title="#{msgs.menu_title}">
  <h:panelGrid columns="1">
    <h:link outcome="providerList"
        value="#{msgs.menu_provider_list}"/>
    <h:link outcome="showCustomer"
        value="#{msgs.menu_show_customer}"/>
  </h:panelGrid>
</mc:panelBox>
<mc:panelBox id="news" title="#{msgs.news_title}">
  <p>MyGourmet - jetzt mit Facelets und Templating</p>
</mc:panelBox>
Trifft Facelets beim Aufbau der Ansicht in der Seitendeklaration auf eine Kompositkomponente, wird im Standardfall eine Komponente vom Typ UINamingContainer erzeugt und in den Komponentenbaum eingefügt (Abschnitt [Sektion:  Kompositkomponenten und klassische Komponenten kombinieren] zeigt, wie Sie an dieser Stelle eine eigene Komponente verwenden können). Diese Komponente bildet den Wurzelknoten für sämtliche weitere Inhalte der Kompositkomponente. Ihre Kinder werden allerdings erst beim weiteren Abarbeiten der Seitendeklaration aus dem Inhalt der Kompositkomponente selbst und dem Inhalt des Tags in der aufrufenden Seite erstellt und hinzugefügt. Diese Vorgehensweise ist mit dem Aufbau einer Ansicht mit Templates vergleichbar.
Was heißt das konkret für unser Beispiel? Nach dem Erstellen des Wurzelknotens setzt Facelets die Verarbeitung mit panelBox.xhtml fort. Nachdem das div -Element und der Absatz mit dem Titel verarbeitet wurden, kommt das Tag cc:insertChildren an die Reihe. Wie Sie in Listing Seitenleiste in MyGourmet mit panelBox sehen, haben beide panelBox -Tags Inhalte, die an dieser Stelle in den Komponentenbaum aufgenommen werden.

6.1.2 Der Bereich cc:interface

Der Bereich cc:interface definiert die Schnittstelle der Komponente nach außen und umfasst alle Merkmale, die für Benutzer der fertigen Komponente relevant sind. Neben der Definition von Attributen und Facets ist es auch möglich, das Verhalten einzelner interner Komponenten nach außen bekanntzugeben.
Hier eine Liste aller Tags, die in der Schnittstelle einer Kompositkomponente verwendet werden können:
Nach diesem kurzen Überblick folgen jetzt noch einige Details zu den Möglichkeiten der einzelnen Tags. Zu diesem Zweck finden Sie in Listing Kompositkomponente simpleInput die Kompositkomponente simpleInput , die aus einem Eingabefeld und einer Submit-Schaltfläche besteht.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
  <cc:interface>
    <cc:attribute name="inputLabel" required="false"
        default="Input"/>
    <cc:attribute name="submitLabel" default="Submit"/>
    <cc:attribute name="action" required="true"
        targets="submit"/>
    <cc:attribute name="value" required="true"/>
    <cc:editableValueHolder name="input" targets="inputText"/>
    <cc:actionSource name="submit"/>
  </cc:interface>
  <cc:implementation>
    <h:outputLabel for="input" value="#{cc.attrs.inputLabel}"/>
    <h:inputText id="inputText" value="#{cc.attrs.value}"/>
    <h:commandButton id="submit"
        value="#{cc.attrs.submitLabel}"/>
  </cc:implementation>
</ui:composition>

6.1.2.1 Attribute

Wenden wir uns zuerst der Definition von Attributen mit dem Tag cc:attribute zu. Die Komponente simpleInput definiert eine Reihe von Attributen unterschiedlicher Ausprägung. Mit required kann gesteuert werden, ob das Attribut beim Einsatz der Komponente verpflichtend anzugeben ist oder nicht. Die Attribute inputLabel und submitLabel sind beide optional, da bei dem einen required explizit auf false gesetzt ist und beim anderen überhaupt fehlt. Für optionale Attribute kann im Attribut default ein Standardwert definiert werden.
Der Typ eines Attributs kann im Attribut type in Form eines voll qualifizierten Klassennamens angegeben werden. Der Standardwert für type ist java.lang.Object .
Attribute können auch Method-Expressions für Action-Methoden oder Event-Listener enthalten. Dazu muss lediglich im Attribut method-signature die Signatur der Methode angegeben werden. Dabei ist es wichtig, immer voll qualifizierte Klassennamen für den Rückgabewert und die Parameter zu verwenden. Wie das Beispiel in Listing Method-Expressions als Attribute zeigt, wird die übergebene Method-Expression analog zu anderen Attributen direkt über cc.attrs referenziert. Es macht keinen Sinn, type und method-signature gleichzeitig in cc:attribute zu verwendet. Sollte das dennoch einmal der Fall sein, wird der Wert von method-signature einfach ignoriert.
<cc:interface>
  <cc:attribute name="submitAction"
      method-signature="java.lang.String action()"/>
</cc:interface>
<cc:implementation>
  <h:commandButton action="#{cc.attrs.submitAction}"/>
</cc:implementation>
Attribute mit den Namen action , actionListener , validator und , die bereits aus vorherigen Kapiteln bekannt sind, nehmen hier eine Sonderstellung ein. JSF verknüpft in solchen Attributen Method-Expressions automatisch mit Komponenten im Bereich cc:implementation , deren IDs im Attribut targets angegeben sind. Der Wert von targets muss nicht auf eine ID beschränkt sein, sondern kann auch eine Liste von IDs enthalten, die durch Leerzeichen separiert sind. Das Attribut method-signature kann in diesen Fällen leer bleiben, da die Methoden-Signaturen ohnehin von JSF vorgegeben sind.
Im Beispiel aus Listing Kompositkomponente simpleInput ist das Attribut mit dem Namen action über den Wert in targets an die Komponente mit der ID submit gebunden. Das Attribut action im Tag h:commandButton darf in diesem Fall nicht explizit gesetzt sein. Der Vorteil dieser Variante ist, dass Benutzer der Komponente im Attribut action sowohl einen String als auch eine Method-Expression angeben können - genau so wie es zum Beispiel auch bei h:commandButton funktioniert.
So weit, so gut. Der Ansatz mit dem Attribut targets hat in JSF 2.0 allerdings einen entscheidenden Nachteil. Die spezielle Behandlung der oben genannten Attribute action , actionListener , validator und valueChangeListener funktioniert nur, wenn das Attribut der Kompositkomponente genau einen dieser Namen hat. Aus diesem Grund ist es mit JSF 2.0 zum Beispiel auch nicht möglich, mehr als ein "echtes" action -Attribut zu definieren. Abschnitt [Sektion:  Fallstricke in der Praxis] zeigt, wie dieses Problem mit JSF 2.0 umgangen und ab JSF 2.1 gelöst werden kann.
An dieser Stelle möchten wir Sie noch darauf hinweisen, dass Facelets alle Attribute in der Kompositkomponente ablegt, die der Benutzer des Tags angibt - auch wenn sie nicht mit cc:attribute definiert sind. Wir raten Ihnen aber dazu, alle Attribute zu definieren. Damit geben Sie Benutzern der Kompositkomponente einen klaren Kontrakt über die Schnittstelle und das Verhalten in die Hand.

6.1.2.2 Facets

Mit cc:facet kann ein benanntes Facet für eine Kompositkomponente definiert werden. Der Name des Facets wird dabei im Attribut name angegeben. Ist das Attribut required explizit auf true gesetzt, muss der Benutzer der Komponente das Facet verpflichtend hinzufügen. Das Beispiel in Listing Kompositkomponente simpleInputim Einsatz zeigt den Bereich cc:interface einer Kompositkomponente mit zwei Facet-Definitionen. Abschnitt [Sektion:  Der Bereich cc:implementation] zeigt Details zu Facets in Kompositkomponenten.
<cc:interface>
  <cc:facet name="header" required="true"/>
  <cc:facet name="footer"/>
</cc:interface>

6.1.2.3 Verhaltensdefinitionen

Interne Komponenten im Bereich cc:implementation , die ein spezielles Verhaltens-Interface implementieren (siehe Abschnitt Sektion:  Verhaltens-Interfaces für Details), können mit den Tags cc:actionSource , cc:editableValueHolder und cc:valueHolder nach außen bekannt gegeben werden. Damit bekommen Benutzer der Kompositkomponente die Möglichkeit, Objekte wie Event-Listener, Konverter oder Validatoren an diese Komponente zu binden. Das Beispiel aus Listing Kompositkomponente simpleInput definiert eine Action-Source mit dem Namen submit und einen Editable-Value-Holder mit dem Namen input .
Der Editable-Value-Holder mit dem Namen input ist über den Wert des Attributs targets an das Eingabefeld mit der ID inputText gebunden - hier ist auch eine Liste von IDs möglich, die durch Leerzeichen separiert sind. Wenn sich der Name und die ID der verbundenen Komponente nicht unterscheiden, kann das Attribut targets auch wegfallen. Bei der Action-Source mit dem Namen submit trifft genau das zu: Die Schaltfläche mit der ID submit ist an die Action-Source gebunden.
Bleibt noch zu klären, wie Benutzer von Kompositkomponenten Event-Listener, Konverter oder Validatoren an die Kompositkomponente anhängen können. In JSF 2.0 haben zu diesem Zweck f:actionListener , f:valueChangeListener sowie alle Konverter- und Validator-Tags in der Core-Tag-Library das Attribut for bekommen. Darin kann der Name eines Action-Listeners, eines Value-Holders oder eines Editable-Value-Holders angegeben werden. Listing Kompositkomponente simpleInputim Einsatz zeigt ein Beispiel für den Einsatz der Komponente simpleInput mit einem Action-Listener, einem Validator und einem Value-Change-Listener.
<mc:simpleInput action="#{customerBean.save}"
    value="#{customerBean.longValue}" submitLabel="Save">
  <f:actionListener for="submit"
      binding="#{customerBean.saveListener}"/>
  <f:validateLongRange for="input" minimum="10"/>
  <f:valueChangeListener for="input"
      binding="#{customerBean.valueChangeListener}"/>
</mc:simpleInput>
Aktiviert ein Benutzer im Browser die Schaltfläche, wird in der Process-Validations-Phase zuerst der Validator aufgerufen. Wenn der Wert gültig ist und sich geändert hat, kommt anschließend der Value-Change-Listener zum Zug. In der Invoke-Application-Phase wird dann zuerst der Listener mit for="submit" und dann die in action angegebene Action-Methode ausgeführt.

6.1.3 Der Bereich cc:implementation

Der Bereich cc:implementation enthält alle JSF-Tags, HTML-Elemente und anderweitigen Inhalte, aus denen die Kompositkomponente aufgebaut ist.
Im Implementierungsteil gibt es mehrere Möglichkeiten, Inhalte einzufügen, die ein Benutzer der Kompositkomponente innerhalb der Komponenten-Tags angegeben hat. Folgende Tags der Composite-Tag-Bibliothek sind dafür relevant:
Eine wichtige Rolle spielt im Implementierungsteil das implizite Objekt cc , mit dem in EL-Ausdrücken die aktuelle Kompositkomponente referenziert werden kann. Die wichtigsten Eigenschaften dieses Objekts sind cc.attrs zum Zugriff auf die Attribute der Kompositkomponente und cc.facets zum Zugriff auf Facets. Da beide Eigenschaften vom Typ Map sind, kann direkt mit der Punktnotation auf einzelne Elemente zugegriffen werden. Das implizite Objekt cc referenziert die Wurzelkomponente der Kompositkomponente und hat daher den Typ UIComponent . Dadurch ist es natürlich möglich, auch andere Eigenschaften der Komponente wie cc.clientId zu verwenden.
Auf den praktischen Aspekt des Implementierungsteils werden wir an dieser Stelle nicht weiter eingehen. Was aber nicht heißen soll, dass wir ihn vernachlässigen. Wir möchten Sie für weiterführende Beispiele nur auf die nächsten Abschnitte verweisen, in denen wir einige Kompositkomponenten präsentieren.

6.1.4 Ressourcen in Kompositkomponenten

Bevor wir uns im nächsten Abschnitt tatsächlich auf die konkreten Beispiele stürzen, werfen wir noch einen Blick auf den Einsatz von JSF-Ressourcen in Kompositkomponenten. Nachdem wir uns mit der Komponente bereits in einer Bibliothek befinden, ist es ein Leichtes, dort zusätzliche Bilder, Stylesheets oder Skripte unterzubringen.
Die Ressourcen werden wie in Abschnitt Sektion:  Ressourcen im Einsatz beschrieben mit den Tags h:graphicImage , h:outputScript und h:outputStylesheet in die Komponente eingebunden. Der Vorteil dieser Lösung ist, dass sich Benutzer der Komponente keine Gedanken darüber machen müssen. Wenn sie h:head und h:body verwenden, werden die referenzierten Ressourcen automatisch in die Ansicht aufgenommen. Listing Ressourcen in einer Kompositkomponente zeigt ein Beispiel mit dem Skript script.js und dem Stylesheet style.css , die beide zusätzlich zur Kompositkomponente in der Bibliothek mygourmet liegen.
<cc:implementation>
  <h:outputScript library="mygourmet"
      name="script.js" target="head"/>
  <h:outputStylesheet name="style.css" library="mygourmet"/>
</cc:implementation>
Ab JSF 2.1 gibt es die Möglichkeit, mit dem Namen this die Bibliothek der aktuellen Kompositkomponente zu referenzieren. Das funktioniert allerdings nur, wenn die Ressource über einen EL-Ausdruck im Attribut value referenziert wir (siehe Abschnitt Sektion:  Ressourcen im Einsatz ). Damit lässt es sich vermeiden, den Namen der Bibliothek direkt anzugeben und die Komponente funktioniert auch mit geändertem Bibliotheksnamen ohne Probleme. Listing Bibliotheksname this zeigt das Beispiel aus Listing Ressourcen in einer Kompositkomponente mit der Bibliothek this .
<cc:implementation>
  <h:outputScript target="head" 
      value="#{resource['this:script.js']}"/>
  <h:outputStylesheet value="#{resource['this:style.css']}"/>
</cc:implementation>
Die Referenzierung von Ressourcen mit dem Bibliotheksnamen this funktioniert nur innerhalb von Kompositkomponenten.

6.1.5 Die Komponente mc:panelBox

Zu Beginn dieses Abschnitts haben wir bereits eine einfache Version der Kompositkomponente panelBox gezeigt, die wir noch um einige Aspekte erweitern werden. Listing Finale Version der Kompositkomponente panelBox zeigt die komplette Komponente.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:cc="http://xmlns.jcp.org/jsf/composite"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
        xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<cc:interface>
  <cc:attribute name="styleClass" default="box"/>
  <cc:attribute name="style" required="false"/>
  <cc:attribute name="headerClass" default="box-header"/>
  <cc:facet name="header" required="false"/>
</cc:interface>
<cc:implementation>
  <h:outputStylesheet library="mygourmet"
       name="components.css"/>
  <div class="#{cc.attrs.styleClass}"
      style="#{cc.attrs.style}">
    <c:if test="#{!empty cc.facets.header}">
      <p class="#{cc.attrs.headerClass}">
        <cc:renderFacet name="header"/>
      </p>
    </c:if>
  <cc:insertChildren/>
</div>
</cc:implementation>
</ui:composition>
In der einfachen Variante der Komponente war der Header noch als Attribut implementiert. Aus Gründen der Flexibilität ist der Header jetzt ein Facet der Komponente. Damit ist es auch möglich, komplexere Inhalte in den Header zu verfrachten. Die Definition des Facets erfolgt im Interface-Bereich der Komponente mit dem Tag cc:facet . In der gerenderten Ausgabe soll der Inhalt des Facets in einem Absatz dargestellt werden - allerdings nur, wenn der Benutzer das Facet angegeben hat. Sehen wir uns dieses Problem Schritt für Schritt an. Das Einfügen des Facets erfolgt mit cc:renderFacet und macht keine Probleme, wenn der Benutzer nichts angegeben hat. Problematisch ist erst das umschließende p -Element, das natürlich nicht in der Ausgabe auftauchen soll, wenn es kein Facet gibt. Die Lösung ist einfach. Mit einem c:if -Tag wird mit dem Ausdruck #{!empty cc.facets.header} überprüft, ob das Facet header angegeben wurde. Ist das nicht der Fall, wird der Block innerhalb c:if nicht in den Komponentenbaum eingefügt.
Der zweite interessante Aspekt dieser Kompositkomponente ist das Styling mit CSS. Alle Komponenten in der Bibliothek mygourmet sollen ein einheitliches Styling erhalten, das allerdings von geändert werden kann. In der Komponente definieren wir mit dem Tag h:outputStylesheet eine Abhängigkeit auf das Stylesheet components.css . Fürs Styling kommen die Attribute styleClass und headerClass mit Defaultwerten zum Einsatz. So ist gewährleistet, dass die internen CSS-Klassen bei Bedarf überschrieben werden können.
Die Deklaration der beiden panelBox -Komponenten in der Seitenleiste von MyGourmet 13 ist in Listing Kompositkomponente panelBox im Einsatz zu sehen. Abbildung Gerenderte Ausgabe von panelBox in MyGourmet 13 zeigt die gerenderte Ausgabe mit Default-Styling.
<mc:panelBox id="menu">
  <f:facet name="header">
    <h:outputText value="#{msgs.menu_title}"/>
  </f:facet>
  <h:panelGrid columns="1">
    <h:link outcome="providerList"
        value="#{msgs.menu_provider_list}"/>
    <h:link outcome="showCustomer"
        value="#{msgs.menu_show_customer}"/>
  </h:panelGrid>
</mc:panelBox>
<mc:panelBox id="news">
  <f:facet name="header">
    <h:outputText value="#{msgs.news_title}"/>
  </f:facet>
  <p>MyGourmet - jetzt mit Facelets und Templating</p>
</mc:panelBox>
Abbildung:Gerenderte Ausgabe von panelBox in MyGourmet 13

6.1.6 Die Komponente mc:dataTable

Die Komponente mc:dataTable zeigt, wie Kompositkomponenten die tägliche Arbeit mit JSF erleichtern können. mc:dataTable bietet keine neue Funktionalität, sondern stellt eine Art Wrapper für h:dataTable dar. Damit ist es möglich, die Standardkomponente mit einem Default-Style zu erweitern. Listing Kompositkomponente dataTable zeigt die Deklaration der Komponente.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
    xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<cc:interface>
  <cc:attribute name="var"/>
  <cc:attribute name="value"/>
  <cc:facet name="header"/>
  <cc:facet name="footer"/>
</cc:interface>
<cc:implementation>
  <h:outputStylesheet library="mygourmet"
      name="components.css"/>
  <h:dataTable id="table" value="#{cc.attrs.value}"
      styleClass="mygourmet-table" headerClass=
      "mygourmet-table-header" rowClasses=
      "mygourmet-table-rownobg, mygourmet-table-rowbg"
      columnClasses="mygourmet-table-cell">
    <c:set target="#{component}" property="var"
        value="#{cc.attrs.var}"/>
    <cc:insertFacet name="header"/>
    <cc:insertChildren/>
    <cc:insertFacet name="footer"/>
  </h:dataTable>
</cc:implementation>
</ui:composition>
Damit die Tabelle auch Daten anzeigt, brauchen wir die Attribute var und value in der Schnittstelle der Kompositkomponente, die dann im Implementierungsteil an h:dataTable weitergegeben werden. Hier ergibt sich allerdings ein Problem. Das Attribut var von h:dataTable darf nicht über eine Value-Expression gesetzt werden. Deshalb müssen wir hier einen kleinen Trick anwenden. Facelets bietet mit der JSTL-Funktion c:set ein Tag, um Eigenschaften von Beans zu setzen. Ab JSF 2.0 steht die aktuelle Komponente direkt mit dem impliziten Objekt component zur Verfügung. Mit c:set als direktes Kind von h:dataTable ist es somit möglich, das Attribut var doch zu setzen.
Die CSS-Klassen werden bei mc:dataTable direkt gesetzt, könnten aber wie bei der Komponente panelBox auch mit Attributen, die über Defaultwerte verfügen, realisiert werden. Das Stylesheet components.css wird genau wie zuvor als Ressource eingebunden.
Der Implementierungsteil zeigt deutlich den Unterschied zwischen cc:renderFacet und cc:insertFacet . Im aktuellen Beispiel kommt cc:insertFacet zum Einsatz, um die Facets header und footer an h:dataTable weiterzureichen. Sie sollen ja nicht von der Kompositkomponente, sondern von h:dataTable gerendert werden. Zu guter Letzt bleibt noch das Tag compo-site:insertChildren übrig, mit dem die Kindelemente des Tags in der aufrufenden Deklaration an h:dataTable werden. mc:dataTable wird dadurch wie h:dataTable verwendet, in dem der Inhalt mit h:column -Tags deklariert wird.
Abbildung Gerenderte Ausgabe von dataTable zeigt die gerenderte Ausgabe von mc:dataTable aus der Ansicht providerList.xhtml .
Abbildung:Gerenderte Ausgabe von dataTable

6.1.7 Die Komponente mc:collapsiblePanel

Die Kompositkomponente mc:collapsiblePanel rendert einen Bereich der Seite, der über eine Schaltfläche ein- und ausgeblendet werden kann. Diese Schaltfläche wird als Icon gerendert, das sich je nach Einklappzustand des Panels ändert. Direkt neben dem Icon wird der Inhalt des optionalen Facets header dargestellt. Listing Kompositkomponente collapsiblePanel zeigt die Deklaration der Komponente.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<cc:interface>
  <cc:attribute name="model" required="true">
    <cc:attribute name="collapsed" required="true"/>
    <cc:attribute name="toggle" required="true"
        method-signature="java.lang.String f()"/>
  </cc:attribute>
  <cc:actionSource name="toggle"/>
  <cc:facet name="header"/>
</cc:interface>
<cc:implementation>
  <h:panelGroup layout="block"
      styleClass="collapsiblePanel-header">
    <h:commandButton id="toggle"
        action="#{cc.attrs.model.toggle}"
        styleClass="collapsiblePanel-img"
        image="#{resource[cc.attrs.model.collapsed
          ? 'mygourmet:toggle-plus.png'
          : 'mygourmet:toggle-minus.png']}"/>
    <cc:renderFacet name="header"/>
  </h:panelGroup>
  <h:panelGroup layout="block"
      rendered="#{!cc.attrs.model.collapsed}">
    <cc:insertChildren/>
  </h:panelGroup>
</cc:implementation>
</ui:composition>
Das Ein- und Ausklappen des Inhalts der Komponente erfolgt mithilfe der Attribute collapsed , das den Einklappzustand steuert, und toggle , das die Action-Methode zum Umschalten des Einklappzustands aufnimmt. Die beiden Attribute sind in das Attribut model eingebettet, wodurch Benutzer der Komponente nur eine Bean übergeben müssen, die über die Eigenschaft collapsed und eine Methode mit der in toggle definierten Signatur verfügt.
Der Inhalt des Panels ist in eine h:panelGroup -Komponente eingebettet, mit deren rendered -Attribut das Ein- und Ausblenden realisiert wird. Der Wert dieses Attributs wird dazu mit dem Ausdruck #{!cc.attrs.model.collapsed} vom übergebenen Zustand abhängig gemacht. Beachten Sie hier bitte auch den Zugriff auf das geschachtelte Attribut der Kompositkomponente.
Die Schaltfläche zum Umschalten des Einklappzustands wird in Form von Bildern gerendert. Wie schon das Stylesheet aus den letzten Beispielen liegen die beiden Bilder auch direkt in der Ressourcenbibliothek. Sie werden im Attribut image des h:commandButton -Tags über das implizite Objekt resource eingebunden. Die Schaltfläche selbst ist unter dem Namen toggle als Action-Source nach außen verfügbar.
Listing Kompositkomponente collapsiblePanelim Einsatz zeigt ein Einsatzszenario der Komponente und in Abbildung Gerenderte Ausgabe von collapsiblePanel finden Sie die gerenderte Ausgabe beider Einklappzustände.
<mc:collapsiblePanel model="#{customerBean}">
  <f:facet name="header"><h3>Information</h3></f:facet>
  Diese Information ist klappbar.<br/>
  Diese Information ist klappbar.
</mc:collapsiblePanel>
Abbildung:Gerenderte Ausgabe von collapsiblePanel
Die hier gezeigte Kompositkomponente collapsiblePanel funktioniert so weit einwandfrei, hat aber noch einen entscheidenden Schönheitsfehler. Die Logik zum Ein- und Ausblenden der Kindkomponenten muss vom Benutzer der Komponente über das Attribut model bereitgestellt werden. Das entspricht nicht unserer ursprünglichen Definition von Komponenten als eigenständige und wiederverwendbare Bausteine. Aus diesem Grund werden wir in Abschnitt [Sektion:  Kompositkomponenten und klassische Komponenten kombinieren] die Komponente um diese Funktionalität erweitern. Dazu zeigen wir Ihnen aber vorher noch in Abschnitt [Sektion:  Klassische Komponenten] das Erstellen von klassischen Komponenten.

6.1.8 Die Komponente mc:inputSpinner

Die Kompositkomponente mc:inputSpinner rendert eine Eingabekomponente mit zwei Buttons, die über JavaScript den Zahlenwert des Eingabefelds erhöhen oder reduzieren. Die Schnittstelle der Komponente nach außen ist sehr übersichtlich. Sie umfasst die Attribute value für die Eingabekomponente und inc , das den Betrag definiert, der addiert beziehungsweise subtrahiert wird. Zusätzlich ist die Eingabekomponente unter dem Namen input verfügbar. Listing Kompositkomponente inputSpinner zeigt die Deklaration.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://xmlns.jcp.org/jsf/html"
  xmlns:cc="http://xmlns.jcp.org/jsf/composite"
  xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<cc:interface>
  <cc:attribute name="value" required="true"
      type="java.lang.Integer"/>
  <cc:attribute name="inc" default="1"/>
  <cc:editableValueHolder name="input"/>
</cc:interface>
<cc:implementation>
  <h:outputStylesheet library="mygourmet"
      name="components.css"/>
  <h:outputScript library="mygourmet"
      name="inputSpinner.js" target="head"/>
  <h:panelGroup>
    <h:inputText id="input" value="#{cc.attrs.value}"
        styleClass="inputSpinner-input"/>
    <h:panelGroup id="buttons"
        styleClass="inputSpinner-buttons">
      <h:graphicImage styleClass="inputSpinner-button"
          name="spin-up.png" library="mygourmet"
          onclick="return changeNumber(
          '#{cc.clientId}:input', #{cc.attrs.inc});"/>
      <h:graphicImage styleClass="inputSpinner-button"
          name="spin-down.png" library="mygourmet"
          onclick="return changeNumber(
          '#{cc.clientId}:input', #{-cc.attrs.inc});"/>
    </h:panelGroup>
  </h:panelGroup>
</cc:implementation>
</ui:composition>
Der Implementierungsteil umfasst das Einbinden der benötigten Ressourcen, die Eingabekomponente und eine h:panelGroup mit zwei Bildern, die über JavaScript als Schaltfläche zum Ändern des Werts fungieren. Die Komponente lädt also insgesamt vier Ressourcen: Zum einen das bereits bekannte Stylesheet components.css , dann das Skript inputSpinner.js zum Ändern des Werts und die beiden Bilder spin-up.png und spin-down.png .
Das Erhöhen und Reduzieren des Werts der Eingabekomponente erfolgt in der Funktion changeNumber , die als Parameter die Client-ID der Eingabekomponente und den Wert zum Addieren bekommt. Durch das Übergeben der Client-ID ist es ohne Probleme möglich, mehrere input-Spinner -Komponenten in einer Ansicht zu verwenden. Listing JavaScript für inputSpinner zeigt die JavaScript-Funktion aus der Ressource inputSpinner.js .
function changeNumber(clientId, increment) {
  var inc = Number(increment);
  if (isNaN(inc) || inc == 0 ) inc = 1;
  var input = document.getElementById(clientId);
  var val = Number(input.value);
  if (isNaN(val)) val = 0;
  input.value = val + inc;
  return false;
}
Die Funktion wird über das Attribut onclick der beiden Bilder ausgeführt. Interessant ist dort das Berechnen der Client-ID der Eingabekomponente. Da die Kompositkomponente selbst ein Naming-Container ist, muss auch ihre ID berücksichtigt werden. Diese bekommen wir mit dem Ausdruck cc.clientId und durch das Anhängen von :input ergibt sich die Client-ID der Eingabekomponente.
Die Komponente wird wie jede andere Eingabekomponente eingesetzt. In Abbildung Gerenderte Ausgabe von inputSpinner sehen Sie die gerenderte Ausgabe.
Abbildung:Gerenderte Ausgabe von inputSpinner

6.1.9 Fallstricke in der Praxis

Dieser Abschnitt zeigt eine Reihe von Fallstricken, die beim Einsatz von Kompositkomponenten bedacht werden müssen.

6.1.9.1 Kompositkomponenten im Komponentenbaum

Beim Einsatz von Kompositkomponenten wird wie in Abschnitt [Sektion:  Eine erste Kompositkomponente] beschrieben immer eine Wurzelkomponente in den Komponentenbaum eingefügt. In den meisten Fällen ist dieses Verhalten auch erwünscht, um zum Beispiel doppelte IDs bei mehrfachem Einsatz der Komponente zu vermeiden. In wenigen Spezialfällen kann diese Wurzelkomponente allerdings unerwartete Nebeneffekte haben.
Sehen wir uns dazu im folgenden Beispiel die Kompositkomponente inputField an, die eine h:inputText -Komponente mit einer zugeordneten h:outputLabel -Komponente zusammenfasst:
<cc:interface>
  <cc:attribute name="value"/>
</cc:interface>
<cc:implementation>
  <h:outputLabel for="input" value="Input:"/>
  <h:inputText id="input" value="#{cc.attrs.value}"/>
</cc:implementation>
Im folgenden Beispiel werden zwei der oben gezeigten inputField -Komponenten in einer h:panelGrid -Komponente angeordnet:
<h:panelGrid columns="2">
  <mc:inputField value="#{testBean.value1}"/>
  <mc:inputField value="#{testBean.value2}"/>
</h:panelGrid>
Die vermutlich erwartete Ausgabe an dieser Stelle wäre, dass eine Tabelle mit zwei Zeilen gerendert wird, die in der ersten Spalte die Labels und in der zweiten Spalte die Eingabefelder beinhaltet. Das tatsächliche Ergebnis ist allerdings anders: JSF rendert eine Tabelle mit nur einer Zeile. Dieses Verhalten ist aus Sicht von JSF auch korrekt. Das Panel-Grid "sieht" ja nur die Wurzelkomponente der Kompositkomponente und stellt den kompletten Inhalt in einer Zelle dar.
Momentan (einschließlich JSF 2.2) gibt es leider keine Möglichkeit, dieses Verhalten zu umgehen.

6.1.9.2 Verwendung mehrerer Action-Attribute

In Abschnitt [Sektion:  Der Bereich cc:interface] haben wir gezeigt, wie die Attribute action , actionListener , validator und valueChangeListener über das Attribut targets mit internen Komponenten verknüpft werden können. Die spezielle Behandlung dieser Attribute funktioniert jedoch nur, wenn das Attribut der Kompositkomponente genau einen dieser Namen hat. Da der Name des Attributs aber eindeutig sein muss, ist es mit JSF 2.0 nicht möglich, mehr als ein "echtes" action -Attribut zu definieren.
Spätestens dann, wenn wir zu einer Kompositkomponente eine zweite Schaltfläche hinzufügen wollen, brauchen wir aber ein zweites action -Attribut. Als Notlösung kann in JSF 2.0 in diesem Fall ein Attribut mit einer entsprechenden Methodensignatur zum Einsatz kommen wie Abbildung Mehrere Action-Attribute mit JSF 2.0 zeigt.
<cc:interface>
  <cc:attribute name="action" targets="submit"/>
  <cc:attribute name="cancelAction"
      method-signature="java.lang.String action()"/>
</cc:interface>
<cc:implementation>
  <h:commandButton id="submit"/>
  <h:commandButton id="cancel"
      action="#{cc.attrs.cancelAction}"/>
</cc:implementation>
Ab JSF 2.1 gibt es eine elegantere Lösung für dieses Problem. Mit dem bereits bekannten Attribut targets und dem neuen Attribut targetAttributeName kann cc:attribute mit einem bestimmten Attribut einer internen Komponente verknüpft werden. Abbildung Mehrere Action-Attribute mit JSF 2.1 zeigt diesen Ansatz für das obige Beispiel.
<cc:interface>
  <cc:attribute name="submitAction" targets="save"
      targetAttributeName="action"/>
  <cc:attribute name="cancelAction" targets="cancel"
      targetAttributeName="action"/>
</cc:interface>
<cc:implementation>
  <h:commandButton id="submit"/>
  <h:commandButton id="cancel"/>
</cc:implementation>
Der Wert des Attributs submitAction wird in diesem Beispiel mit dem Attribut action der internen Komponente mit der ID submit verknüpft. Analog wird der Wert des Attributs cancelAction an das Attribut action der internen Komponente mit der ID cancel weitergereicht. targetAttributeName enthält also immer dann den Namen des "Zielattributs", wenn sich dieser vom Namen in name unterscheidet.

6.1.9.3 Auflösung des Typs von Attributen

Mit JSF 2.0 und 2.1 kann es zu Problemen mit Attributen von Kompositkomponenten kommen, wenn beim Einsatz der Komponente null als Attributwert übergeben wird. JSF kann in diesem Fall den Typ des übergebenen Werts nicht auflösen, was in bestimmten Situationen zu fehlerhaftem Verhalten führt.
Dieses Fehlverhalten lässt sich anhand eines Beispiels sehr einfach demonstrieren. Die Kompositkomponente inputTest hat ein Attribut mit dem Namen value , das im Implementierungsteil in einer Eingabekomponente benutzt wird:
<cc:interface>
  <cc:attribute name="value"/>
</cc:interface>
<cc:implementation>
  <h:inputText id="input" value="#{cc.attrs.value}"/>
</cc:implementation>
Der Fehler tritt auf, wenn beim Einsatz der Komponente eine Value-Expression verwendet wird, die zu null evaluiert (vorausgesetzt die Eigenschaft longValue hat den Typ java.lang.Long und nicht long ):
<mc:inputTest value="#{testController.longValue}"/>
JSF kann in Version 2.0 und 2.1 den Typ in diesem Fall nicht auflösen. Daher wird in der Eingabekomponente auch kein entsprechender Konverter gesetzt - was sonst automatisch basierend auf dem Typ java.lang.Long gemacht werden würde. Der fehlende Konverter kann zu unerwartetem Verhalten im Lebenszyklus führen. Bei einer ungültigen Benutzereingabe wird dann zum Beispiel statt der Anzeige einer Fehlermeldung eine ClassCastException geworfen.
Mit JSF 2.2 tritt dieses Problem nicht mehr auf, da der Algorithmus zur Auflösung des Typs von Attributen geändert wurde (in Apache MyFaces wird dieser Algorithmus bereits ab den Versionen 2.0.10 und 2.1.4 eingesetzt). Für frühere JSF-Versionen können Sie dieses Problem verhindern, indem Sie in h:inputText explizit einen Konverter verwenden (falls der Typ eindeutig ist) oder indem Sie auf den Wert null für diese Attribute verzichten.

6.2 Klassische Komponenten

Nach der Einführung in das Thema Kompositkomponenten zeigen wir Ihnen in diesem Abschnitt, wie Sie mit JSF eine klassische Komponente für den Einsatz mit Facelets erstellen. Auf den ersten Blick kann dieser Vorgang etwas kompliziert wirken, da er eine Reihe von Schritten umfasst - vor allem auch wegen der vielen Webtechnologien, die dabei verwendet werden.
Die Erstellung einer Komponente besteht aus folgenden Schritten:
Jeden einzelnen dieser Punkte werden wir uns jetzt anhand eines kleinen Beispiels genauer ansehen. Zu Demonstrationszwecken erstellen wir die Kompositkomponente inputSpinner nochmals als klassische JSF-Komponente.

6.2.1 Vorarbeiten: Komponentenfamilie, Komponententyp und Renderertyp definieren

Bevor wir mit dem Schreiben der Komponente beginnen, sollten wir uns darüber klar werden, ob und wie die Komponente von einer anderen Komponente ableitbar ist oder ob wir vollständig "von der grünen Wiese" weg beginnen müssen. Üblicherweise ist es sinnvoll, von einer bestehenden Basiskomponente abzuleiten.

6.2.1.1 Die Wahl der Basisklasse

Kandidaten für die Basisklasse sind von UIComponent abgeleitete Klassen, im Standard also die folgenden Komponentenklassen:
Häufig wird von einer der drei Komponenten UICommand , UIInput oder UIPanel abgeleitet, weil diese drei Klassen bereits sehr viel Logik für das Arbeiten mit häufig wiederkehrenden Aufgaben bereitstellen.
Wann ist das Ableiten von UIInput sinnvoll? Das ist dann angebracht, wenn die Komponente genau einen Wert aus dem Modell präsentieren soll. Dinge wie Konvertierung, Validierung, Schreiben und Lesen des Werts aus und in das Modell werden von dieser Klasse bereits erledigt. Ist das Verhalten der Komponente noch spezieller, kann man eventuell von der Klasse HtmlInputText ableiten - diese Klasse und der zugehörige Renderer erledigen auch das Decodieren des Werts aus der HTTP-Anfrage, das Setzen des Werts als Submitted-Value und das Schreiben der Komponentenansicht in die HTTP-Antwort.
Die Klasse UIInput bietet sich ebenfalls an, wenn eine vom Benutzer ausgelöste Veränderung eines Komponentenattributs gespeichert werden soll. Damit kann zum Beispiel eine einklappbare Panel-Komponente realisiert werden, die ihren Einklappzustand im Attribut value ablegt. Dadurch kann der Zustand auch an eine Bean-Eigenschaft gebunden werden.
Die UICommand -Klasse wird man als Basis für die eigene Komponente verwenden, wenn diese "Aktionen" auslöst. Die Behandlung von Action-Methoden, Action-Listenern und das Auslösen von Ereignissen werden durch diese Basisklasse bereits erledigt.
Die Wahl der Basisklasse für den Input-Spinner fällt eindeutig aus. Nachdem es sich um ein "getuntes" Eingabefeld handelt, drängt sich UIInput auf.

6.2.1.2 Komponententyp, Komponentenfamilie und Renderertyp

Jede Komponente ist im System eindeutig über ihren Komponententyp identifiziert. Es darf keine andere Komponente mit demselben Komponententyp existieren. Üblicherweise wird der Komponententyp als statische Konstante definiert. Unsere Input-Spinner-Komponente, deren Komponentenklasse InputSpinner wir im nächsten Abschnitt erstellen, hat folgenden Komponententyp:
public static final String COMPONENT_TYPE =
    "at.irian.InputSpinner";
Beim Registrieren der Komponente im System wird ihr Komponententyp als Bezeichner verwendet. Dieser kommt dann zum Beispiel bei der Definition des Tags der Komponente in der Tag-Bibliothek zum Einsatz.
Darüber hinaus können wir den Komponententyp übergeben, wenn wir eine Komponente erzeugen wollen und dazu nicht direkt den Konstruktor, sondern die Factory-Methode createComponent() der Ap-plication -Klasse aufrufen. Tatsächlich sollte man immer über diesen Weg gehen, damit zentral die Implementierung einer Komponente ausgetauscht werden kann. Hier das Beispiel dafür:
FacesContext.getCurrentInstance().getApplication()
    .createComponent(InputSpinner.COMPONENT_TYPE);
Der nächste Schritt ist die Definiton der Komponentenfamilie - hier kann entweder eine bestehende Familie verwendet oder eine neue Familie definiert werden. Die Familie einer Komponente wird mit einem Aufruf von UIComponent.getFamily() bestimmt. Wenn Sie eine eigene Familie für Ihre Komponente erstellen wollen, müssen Sie diese Methode überschreiben. In einer Komponentenfamilie können mehrere Komponenten enthalten sein. Nachdem es sich bei unserer Beispielkomponente um ein Eingabefeld handelt, belassen wir es bei der von UIInput geerbten Familie javax.faces.Input .
Die Komponentenfamilie wird gemeinsam mit dem Renderertyp verwendet, um einen Renderer auszuwählen. Würde hier der Komponententyp benutzt werden, könnte ein Renderer immer nur eine Art von Komponente rendern - durch die Definition der Komponentenfamilie kann für eine ganze Gruppe von Komponenten derselbe Renderer zum Einsatz kommen. Der Renderertyp wird bei der Definition des Tags gemeinsam mit dem Komponententyp angegeben und beim Erzeugen der Komponente gesetzt - damit wird der oft im Komponentenkonstruktor gesetzte "Default"-Renderertyp überschrieben.
Im Folgenden sehen Sie den Komponententyp, die Komponentenfamilie und den Renderertyp für ausgewählte Standardkomponenten:
Unsere Input-Spinner-Komponente erhält den eigenen Renderertyp at.irian.InputSpinner .

6.2.2 Komponentenklasse schreiben

Die Komponentenklasse ist das "Herzstück" der Komponentenarchitektur von JSF - die Instanz der Komponentenklasse wird im JSF-Komponentenbaum gespeichert und beinhaltet alle Daten einer Komponente. Nachdem die Komponentenklasse die eigentlich "treibende Kraft" im Ablauf einer JSF-Anfrage ist In jeder Phase des Lebenszyklus wird der Komponentenbaum durch rekursive Aufrufe der zuständigen Behandlungsmethoden durchwandert.: , kann man alle Ereignisse in JSF von hier aus steuern.
Zwei der wichtigsten Aufgaben sind das Auslesen der für die Komponente relevanten Request-Parameter (Decoding) und das Rendern der Komponente (Encoding). Die Komponente kann - wenn das vom Entwickler so vorgesehen ist - diese Aufgaben selbst übernehmen oder an einen Renderer delegieren. Der größte Vorteil eines eigenen Renderers ist die klare Trennung zwischen den in der Komponente enthaltenen Daten und der daraus gerenderten Ausgabe. Die beiden Ansätze schließen sich nicht gegenseitig aus. So wäre es zum Beispiel vorstellbar, in einer ersten Version das Rendering von der Komponente durchführen zu lassen und erst später einen eigenen Renderer zu erstellen.
Wir werden uns in diesem Abschnitt um die Komponentenklasse kümmern und erst in Abschnitt [Sektion:  Rendererklasse schreiben] einen genaueren Blick auf die Rendererklasse werfen.

6.2.2.1 Komponentenattribute

Eine Komponentenklasse enthält üblicherweise eine Eigenschaft für jedes Attribut des zugehörigen Tags. Es kann aber auch Attribute des Tags geben, die nicht explizit in der Komponente existieren - diese werden dann in einer speziellen Map in der Komponente abgelegt.
Die Zugriffsmethoden auf die Eigenschaften der Komponente müssen gewährleisten, dass die Eigenschaften sowohl direkt als auch über Value-Expressions angegeben werden können. Dabei gilt, dass das direkte Setzen von Attributen stärker "zieht" als das Setzen einer Value-Expression. Beim Lesen des Komponentenattributs muss dann natürlich berücksichtigt werden, ob es direkt gesetzt wurde. Ist das der Fall, wird der Komponentenwert direkt zurückgegeben, ansonsten wird die Value-Expression evaluiert. Klingt kompliziert - ist aber mit JSF 2.0 zum Kinderspiel geworden.
Ab JSF 2.0 werden als Grundlage für das neue Partial-State-Saving Eigenschaften von Komponenten nicht mehr in privaten Feldern, sondern in einer Map verwaltet. Zu diesem Zweck hat jede Komponente eine Instanz der Klasse StateHelper , die den Zustand intern verwaltet. Was es mit dem Zustand auf sich hat, klären wir etwas weiter unten, jetzt interessiert uns erst einmal das Lesen und Schreiben der Eigenschaften.
Listing Die Klasse InputSpinner zeigt die Klasse InputSpinner unserer Beispielkomponente. Wie Sie sehen, ist der Code sehr überschaubar und umfasst im Grunde genommen nur die Definition der Eigenschaft inc . Den Rest, wie etwa das State-Saving oder das Verwalten der anderen Attribute, erledigen die Basisklassen von JSF. Nachdem wir von UIInput abgeleitet haben, können wir auch dessen bereits vorhandenen Funktionsumfang benutzen.
public class InputSpinner extends UIInput {

  public static final String COMPONENT_TYPE =
      "at.irian.InputSpinner";

  enum PropertyKeys {inc}

  public InputSpinner() {
    setRendererType("at.irian.InputSpinner");
  }
  public int getInc() {
    return (Integer)getStateHelper().eval(
        PropertyKeys.inc, 1);
  }
  public void setInc(int inc) {
    getStateHelper().put(PropertyKeys.inc, inc);
  }
}
Mit der Methode put() wird der Wert der Eigenschaft direkt mit dem Schlüssel PropertyKeys.inc gesetzt. Die Methode eval() liefert, falls vorhanden, den direkt gesetzten Wert zurück. Existiert ein solcher nicht, wird anschließend eine eventuell vorhandene Value-Expression evaluiert. In unserem Fall wird, falls auch eine solche nicht existiert, der Defaultwert 1 zurückgeliefert.

6.2.2.2 State-Saving

Neben den Zugriffsmethoden auf die Eigenschaften der Komponente hat die Komponentenklasse noch eine äußerst wichtige Aufgabe: Sie muss ihren Zustand bis zum nächsten Request sichern können. Das State-Saving ist in früheren JSF-Versionen relativ einfach gestrickt und verfolgt den Ansatz, immer den kompletten Zustand des gesamten Komponentenbaums zu speichern und wiederherzustellen. Da diese Vorgehensweise in Bezug auf Performance und Speicherverbrauch nicht optimal ist, wurde in JSF 2.0 mit dem Partial-State-Saving ein neuer Ansatz realisiert.
Wie der Name bereits erahnen lässt, werden dabei nur noch wirklich relevante Teile des Zustands gespeichert. JSF markiert dazu nach dem Aufbau des Komponentenbaums einen initialen Zustand, der ohnehin durch die Seitendeklaration definiert ist. Der Partial-State setzt sich dann aus allen Änderungen am Komponentenbaum nach Erreichen dieses Zustands zusammen. Voraussetzung für die korrekte Initialisierung ist der Aufbau der Ansicht aus der Seitendeklaration vor jedem Wiederherstellen des Zustands.
Für das State-Saving sind die Methoden saveState() und restoreState() aus dem Interface StateHolder zuständig. Vor JSF 2.0 mussten diese beiden Methoden in mühsamer Kleinarbeit für jede Komponentenklasse erstellt werden - ein fehleranfälliger Prozess, dem wir keine Träne nachweinen. Ab JSF 2.0 sind diese Methoden bereits in UIComponentBase implementiert und müssen nur noch bei speziellen Anforderungen überschrieben werden.
Wie funktioniert das? Die Grundlage für die Aufzeichnung des Zustands bildet der im letzten Abschnitt vorgestellte StateHelper , der den Zustand der Komponente intern verwaltet. Damit ist auch bereits der Grundstein für das Partial-State-Saving gelegt. Die "zentralisierte" Zustandsverwaltung ermöglicht das Festhalten von Änderungen nach Erreichen des initialen Zustands.
Jede Komponente, die Partial-State-Saving einsetzen will, muss das von StateHolder abgeleitete und um Methoden zur Markierung des initialen Zustands erweiterte Interface PartialStateHolder implementieren. Nachdem auch diese Methoden bereits in der Klasse UIComponentBase implementiert und in einigen anderen Basisklassen erweitert werden, müssen Sie sich auch darum keine Gedanken machen.

6.2.2.3 Komposition klassischer Komponenten

Ein interessanter Aspekt der Komponentenentwicklung ist die Komposition von Komponenten zu einem größeren Ganzen: Dazu ist die geeignete Stelle zu finden, in der Kindkomponenten der Elternkomponente hinzugefügt werden können. Im Wesentlichen gibt es hier zwei Möglichkeiten: Die einfachere Variante ist das Hinzufügen von transienten Kindkomponenten beim Rendern der Seite. Transient bedeutet, dass das Attribut transient der Komponente auf true gesetzt wurde, und die Komponente daher im State-Saving -Prozess verschwindet. Auch wenn diese Art der Komposition funktioniert, ist sie doch nicht in allen Fällen optimal, weil eine Art Komponentenfunktionalität in den Renderer ausgelagert wird.
Mit JSF ab Version 2.0 ist es möglich, Kindkomponenten mithilfe des System-Events PostAddToViewEvent zu einer zusammengesetzten Komponente hinzuzufügen. Dieses Ereignis wird ausgelöst, nachdem eine Komponente in den Komponentenbaum eingefügt wurde. Wir werden einen Listener für dieses Ereignis erstellen, in dem wir die zusätzlichen Komponenten hinzufügen.
Listing Komposition klassischer Komponenten mit System-Events zeigt einen Ausschnitt einer Komponentenklasse, die über die Annotation @ListenerFor als Listener für das System-Event PostAddToViewEvent registriert ist. Tritt das Ereignis beim Einfügen der Komponente in die Ansicht auf, wird die im Interface ComponentSystemEventListener definierte Methode processEvent aufgerufen. Da dieses Interface bereits von der Klasse UIComponent implementiert wird, müssen wir nur die entsprechende Methode überschreiben und erweitern.
@FacesComponent("at.irian.MyPanel")
@ListenerFor(systemEventClass = PostAddToViewEvent.class)
public class MyPanel extends HtmlPanelGroup {

  public void processEvent(ComponentSystemEvent ev)
  throws AbortProcessingException {
    if (ev instanceof PostAddToViewEvent) {
      addComponents();
    }
    super.processEvent(ev);
  }
  ...
}

6.2.3 Rendererklasse schreiben

Der Begriff Renderer steht in JSF für eine Klasse, die einer Komponente (oder einer Komponentenfamilie) zugeordnet ist und die Ansicht für diese Komponente erstellt, aber auch den Wert einer Komponente wieder aus der HTTP-Anfrage ausliest und in die Komponenteninstanz überträgt. Jede Rendererklasse muss von der abstrakten Klasse javax.faces.render.Renderer abgeleitet werden und die Methoden überschreiben, die nicht die Standardfunktionalität besitzen sollen.
Der Renderer ist letztlich die Klasse, deren Schreiben am meisten Aufwand bedeutet, weil in ihr die ganze Ansichtslogik programmmiert werden muss - durch die Vielfalt der in der Webanwendungsentwicklung verwendeten Technologien wie HTML, JavaScript, CSS und XML ist diese Ansichtslogik bei größeren Komponenten sehr komplex und unübersichtlich. Das ist auch der Grund für die Spezifizierung des JSF-Standards - der "normale" Webentwickler muss sich um die Entwicklung dieser Ansichtslogik nicht mehr kümmern.
Folgende Aufzählung zeigt die Methoden der Klasse Renderer :
Einige dieser Methoden müssen von (fast) jedem Entwickler einer benutzerdefinierten Komponente überschrieben werden. Je nach Typ der Komponente ist das entweder encodeBegin() oder encodeEnd() . Diese Methoden werden aufgerufen, wenn das zugehörige Tag in der Seitendeklaration geöffnet respektive geschlossen wird. Hier sollte der zur Komponente gehörige HTML-Code geschrieben werden - wenn es sich überhaupt um einen Renderer für HTML handelt. Prinzipiell sind natürlich auch Renderer für andere Ausgabeformate wie WML, XML, XUL oder SVG denkbar.
Der Renderer für unsere Input-Spinner-Komponente wird in der Klasse InputSpinnerRenderer implementiert.

6.2.3.1 Rendern (Encoding)

Listing Die Methode encodeBegin() des Beispielrenderers zeigt die Methode encodeBegin() der Rendererklasse InputSpinnerRenderer . Als Parameter werden der Methode der Faces-Context und die Komponente, für die das Rendern erfolgen soll, übergeben. Das Schreiben der Ansicht erfolgt in den beiden privaten Methoden encodeInput() zum Rendern des Eingabefelds und encodeButtons() zum Rendern der beiden Spin-Buttons. Momentan interessiert uns nur encodeInput() , da dort das input -Element über Aufrufe der Klasse ResponseWriter geschrieben wird - die wiederum erhält man vom momentanen Faces-Context über einen Aufruf der Methode getResponseWriter() .
public void encodeBegin(FacesContext ctx,
    UIComponent component) throws IOException {
  InputSpinner spinner = (InputSpinner)component;
  String clientId = spinner.getClientId();
  encodeInput(ctx, spinner, clientId);
  encodeButtons(ctx, spinner, clientId);
}
private void encodeInput(FacesContext ctx,
    InputSpinner spinner, String clientId)
    throws IOException {
  ResponseWriter writer = ctx.getResponseWriter();
  writer.startElement("input", spinner);
  writer.writeAttribute("id", clientId, null);
  writer.writeAttribute("name", clientId, null);
  Object value = getValue(ctx, spinner);
  if (value != null) {
    writer.writeAttribute("value", value.toString(), null);
  }
  writer.writeAttribute("class", "inputSpinner-input", null);
  writer.endElement("input");
}
In vielen existierenden Komponenten wird üblicherweise die Methode encodeEnd() und nicht die Methode encodeBegin() überschrieben. Das liegt an historischen Gründen in Verbindung mit JSF 1.1 - dort ist erst in der encodeEnd() -Methode sichergestellt, dass alle Kindelemente der gerade geschriebenen JSF-Komponente bereits erstellt und in den Komponentenbaum eingehängt wurden - daher wird üblicherweise die encodeEnd() -Methode für Komponenten mit Kindelementen verwendet. In JSF-Versionen ab 1.2 ist das nicht mehr notwendig, weil hier in der Render-Response-Phase der gesamte Baum bereits aufgebaut ist.
Standardmäßig wird der Komponentenbaum in JSF rekursiv durchlaufen und jede Komponente wird durch einen einmaligen Aufruf der Methode encodeAll() gerendert. Die Methode encodeAll() ruft zuerst die Methode encodeBegin() der aktuellen Komponente auf und überprüft dann, ob die Methode getRendersChildren() den Wert true zurückliefert. Wenn dem so ist, werden nicht die einzelnen Kindkomponenten durchgegangen, sondern die Methode encodeChildren() aufgerufen - damit kann also die Komponente selbst ihre Kinder in die Ansicht schreiben! Ansonsten wird die Methode encodeAll() auf den einzelnen Kindern aufgerufen und damit rekursiv der Baum einen Schritt tiefer in die Ansicht geschrieben. Für Komponenten, die ihre Kindelemente selbst verwalten und in die HTML-Ansicht schreiben wollen, ist es also wichtig, dass ihr Renderer die Methode getRendersChildren() überschreibt und true zurückliefert.
ResponseWriter: Zurück zum Schreiben der Ausgabe unserer Komponente. Dieser Vorgang funktioniert für HTML (und alle von SGML abgeleiteten Dialekte ähnlich) über das Öffnen, Schließen und Schreiben von Attributen von den zur Komponente gehörenden Tags. Das Öffnen eines Tags ist ein einfacher Aufruf des ResponseWriter :
writer.startElement("input", spinner);
startElement(): Zuerst wird an die Methode startElement() die Zeichenkette übergeben, die als Name des Tags verwendet werden soll - in unserem Fall input . Als zweites Attribut wird die Komponente selbst übergeben. Sehr wichtig - hier soll auf keinen Fall null übergeben werden, sondern immer die zugehörige Komponente. Ist das HTML-Tag einer Komponente nicht eins zu eins zuzuordnen - wenn beispielsweise ein Renderer für eine Komponente mehrere HTML-Tags erzeugt -, sollte die Komponente jedem dieser Tags übergeben werden, das Gleiche gilt auch für eventuelle Kind-Tags. Die Information über die dazugehörende Komponente wird von grafischen Entwicklungsumgebungen ausgewertet, um beim Rendering zur Designzeit beispielsweise alle Tags einer Komponente mit einer speziellen Klasse auszuzeichnen.
writeAttribute(): Im nächsten Schritt werden die Attribute des Tags in die HTML-Ansicht geschrieben, dazu dient die Methode writeAttribute() . Auch hier wird wieder der Name des Attributs zuerst übergeben:
writer.writeAttribute("id", clientId, "id");
Auf den Attributsnamen folgt der zu schreibende Wert und schließlich wieder das dazugehörende Attribut der Komponente. Auch diese Verbindung wird von Entwicklungsumgebungen genutzt und sollte nach Möglichkeit gesetzt werden, wenn ein entsprechendes Komponentenattribut vorhanden ist. Ein Beispiel für ein Attribut, wo das nicht möglich ist, ist das onclick -Attribut. Dieses Attribut hat keine Entsprechung in der Komponente, es wird nur in die Map der Attribute eingetragen.
writer.writeAttribute(HTML.ONCLICK_ATTR,
    onClick.toString(), null);
value-Attribut: Wichtig (und etwas anders als die Behandlung der anderen Attribute) ist die Behandlung des value -Attributs. Wenn eine JSF-Anfrage am Server ankommt, wird der Wert einer Komponente decodiert und dieser Wert vorerst in das Feld submittedValue geschrieben. Tritt beim Konvertieren oder Validieren einer Komponente des Komponentenbaums ein Fehler auf, wird die weitere Behandlung der Anfrage abgebrochen und direkt in die Render-Response-Phase gesprungen. Statt jetzt den Wert der Komponente auszugeben, den man über den Aufruf von getValue() erhält, muss man also beim Rendern zuerst prüfen, ob nur der Submitted-Value gesetzt worden ist. Wenn diese Bedingung zutrifft, darf man nur diesen Submitted-Value schreiben.
Konvertierung: Ist nicht der Submitted-Value ausschlaggebend, sondern tatsächlich ein Wert für die Komponente gesetzt, muss vor dem Rendern des Werts noch ein eventuell angegebener Konverter aufgerufen werden, um den Wert in eine Zeichenkette umzuwandeln.
Listing Auslesen des Werts einer Komponente beim Rendern zeigt die Methode getValue() der Klasse InputSpin-nerRenderer , die nach dem soeben beschriebenen Algorithmus den Wert der Komponente für die Anzeige zurückliefert.
private Object getValue(FacesContext ctx,
    InputSpinner spinner) {
  Object submittedValue = spinner.getSubmittedValue();
  if (submittedValue != null) {
    return submittedValue;
  }
  Object value = spinner.getValue();
  Converter converter = getConverter(ctx, spinner);
  if (converter != null) {
    return converter.getAsString(ctx, spinner, value);
  } else if (value != null) {
    return value.toString();
  } else {
    return "";
  }
}
private Converter getConverter(FacesContext ctx,
    UIComponent comp) {
  Converter conv = ((UIInput)comp).getConverter();
  if (conv != null) return conv;
  ValueExpression exp = comp.getValueExpression("value");
  if (exp == null) return null;
  Class valueType = exp.getType(ctx.getELContext());
  if (valueType == null) return null;
  return ctx.getApplication().createConverter(valueType);
}
Unabhängigkeit von JSF-Implementierung: Üblicherweise kann man Funktionalitäten von der darunterliegenden JSF-API ohne Probleme übernehmen. Man sollte aber darauf aufpassen, sich nicht von der konkreten JSF-Implementierung, auf der man die Komponente entwickelt, abhängig zu machen - man sollte also tatsächlich nur die Funktionalität der javax.faces.* -API verwenden.

6.2.3.2 Decodierung (Decoding)

Weiter im Programm: Genauso, wie die Komponente in die HTML-Seite geschrieben wird, muss der Wert der Komponente bei einem Postback wieder aus der HTTP-Anfrage ausgelesen werden können. Auch diese Aufgabe erledigt der Renderer, und zwar mit der Methode de-code() . Listing Decodieren eines Werts zeigt die Methode unseres Beispielrenderers.
public void decode(FacesContext ctx, UIComponent component) {
  Map<String, String> params = ctx
      .getExternalContext().getRequestParameterMap();
  String clientId = component.getClientId();
  String value = params.get(clientId);
  ((UIInput)component).setSubmittedValue(value);
}
Die Vorgehensweise ist recht einfach: Die Map der Request-Parameter wird nach der Client-ID der Komponente durchsucht und der zurückgelieferte Wert wird als Submitted-Value der Komponente gesetzt.
Konvertierung und Validierung: Jetzt geht's weiter im Lebenszyklus der HTTP-Anfrage: Die Komponente muss den Submitted-Value jetzt konvertieren und anschließend validieren. Zum Konvertieren des Werts wird die Methode getConvertedValue() des Renderers aufgerufen (siehe Listing Konvertieren des Werts im Beispielrenderer ).
public Object getConvertedValue(FacesContext ctx,
    UIComponent component, Object submittedValue)
    throws ConverterException {
  Converter converter = getConverter(ctx, component);
  if (converter != null ) {
    return converter.getAsObject(
        ctx, component, (String) submittedValue);
  } else {
    return submittedValue;
  }
}

6.2.3.3 Rendern von Ressourcen

Das Rendern von Ressourcen wie Bildern, Stylesheets oder Skripten ist ein wichtiger Aspekt bei vielen Komponenten. Ab JSF 2.0 gibt es dafür jetzt endlich auch eine standardisierte Lösung, was das Erstellen von Komponenten erheblich vereinfacht. Das Thema Ressourcen haben wir ja schon in Kapitel Kapitel:  Verwaltung von Ressourcen ausführlich behandelt. Dort wurde bereits kurz erwähnt, dass Abhängigkeiten zwischen Ressourcen und Komponenten in Form von Annotationen auf der Komponenten- oder Rendererklasse abgebildet werden können.
Sehen wir uns das für unsere Input-Spinner-Komponente etwas genauer an. Aus Gründen der Einfachheit werden wir die Ressourcen der Kompositkomponente inputSpinner aus der Bibliothek mygourmet mitverwenden. Damit die Komponente richtig dargestellt wird und ordnungsgemäß funktioniert, benötigen wir das Stylesheet components.css und das Skript inputSpinner.js . Um die beiden Bilder zum Erhöhen und Reduzieren des Werts kümmern wir uns später. Für jede der beiden Ressourcen annotieren wir die Rendererklasse mit @ResourceDependency unter Angabe der Bibliothek und des Namens. Das Skript wird zusätzlich mit target="head" in den Header der gerenderten Ausgabe verfrachtet. Listing Ressourcenannotationen auf der Rendererklasse zeigt die Rendererklasse mit den Annotationen. Mehr ist nicht notwendig, um JSF die Ressourcen automatisch verwalten zu lassen - vorausgesetzt natürlich, Sie verwenden h:head und h:body . Sie können die Komponente auch mehrfach auf einer Seite einsetzen, JSF wird sie immer nur einmal rendern.
@ResourceDependencies({
  @ResourceDependency(library = "mygourmet",
      name = "inputSpinner.js", target = "head"),
  @ResourceDependency(library = "mygourmet",
      name = "components.css")}
)
public class InputSpinnerRenderer extends Renderer {
...
}
Die automatische Ressourcenverwaltung stellt auch sicher, dass Ressourcen in der gerenderten Ausgabe immer genau dort landen, wo sie sollen. Ein Stylesheet hat zum Beispiel in einem HTML-Dokument außerhalb von head nichts zu suchen.
Die beiden Bilder spin-up.png und spin-down.png , die wir ebenfalls aus der Bibliothek mygourmet der Kompositkomponente übernehmen, können natürlich nicht einfach über eine Annotation mit der Komponente verbunden werden. Nachdem sie einen Teil der gerenderten Ausgabe der Komponente bilden, müssen sie manuell eingefügt werden. Dazu kommt die Klasse ResourceHandler zum Einsatz, mit der JSF intern Ressourcen verwaltet. Der Zugriff auf den für die Anwendung zuständigen Resource-Handler erfolgt über das Applikationsobjekt.
Das Rendern der Ressource selbst ist dann relativ einfach. Der wichtigste Schritt ist das Erzeugen der Ressource über die Methode createResource() des Resource-Handlers. Diese Methode nimmt als Parameter entweder nur den Namen oder den Namen und die Bibliothek der Ressource und gibt eine Instanz der Klasse Resource zurück, über die wir vollen Zugriff erhalten. Das Bild wird über den Response-Writer als img -Element gerendert, dessen src -Attribut auf eine spezielle Ressource-URL gesetzt ist. Diese URL wird von der Methode Resource.getRequestPath() berechnet. Wenn der Browser beim Darstellen der Seite das Bild mit dieser URL nachlädt, liefert JSF den Inhalt der Ressource an den Client aus. Listing Direktes Rendern von Ressourcen zeigt, wie das Rendern eines der beiden Bilder mit dem zugehörigen JavaScript-Code aussieht.
Application app = ctx.getApplication();
ResourceHandler handler = app.getResourceHandler();
Resource spinUpRes = handler.createResource(
    "spin-up.png", "mygourmet");
String onclickUp = MessageFormat.format(
    "return changeNumber(''{0}'', {1});",
    clientId, spinner.getInc());
writer.startElement("img", spinner);
writer.writeAttribute("class", "inputSpinner-button", null);
writer.writeAttribute("src", spinUpRes.getRequestPath(), null);
writer.writeAttribute("onclick", onclickUp, null);
writer.endElement("img");
Wenn Sie eine große Anzahl von Komponenten schreiben, werden Sie diese wahrscheinlich in Komponenten-bibliotheken als Jar-Dateien verpacken. Mit dem Einsatz von JSF-Ressourcen ist das kein Problem mehr, da die benötigten Klassen und Ressourcen in einer Jar-Datei zusammengefasst werden können. Eine detaillierte Beschreibung dazu finden Sie in Abschnitt [Sektion:  Die eigene Komponentenbibliothek] .

6.2.4 Registrieren der Komponenten- und der Rendererklasse

Die soeben verfassten Komponenten- und Rendererklassen müssen jetzt noch mit einem Eintrag in der faces-config.xml in der Faces-Umgebung registriert werden.
Listing Registrierung einer Komponentenklasse zeigt die Registrierung der Komponentenklasse des Beispiels unter dem Komponententyp at.irian.InputSpinner .
<component>
  <component-type>
    at.irian.InputSpinner
  </component-type>
  <component-class>
    at.irian.jsfatwork.gui.jsf.component.InputSpinner
  </component-class>
</component>
Wie schon bei Konvertern und Validatoren erlaubt JSF ab Version 2.0 das Registrieren von Komponenten mit einer Annotation. Das Annotieren der Komponentenklasse mit @FacesComponent reicht, um die Komponente unter dem im Element value angegebenen Komponententyp zu registrieren. Listing Registrierung einer Komponentenklasse mittels Annotation zeigt den dazu passenden Code.
@FacesComponent("at.irian.InputSpinner")
public class InputSpinner extends UIInput {
...
}
JSF 2.2: Ab JSF 2.2 ist das Element value der Annotation @FacesComponent optional und wird mit einer Namenskonvention ergänzt. Ist es nicht angegeben, benutzt JSF den Klassennamen mit einem kleinen Anfangsbuchstaben als Komponenten-ID. Für das Beispiel aus Listing Registrierung einer Komponentenklasse mittels Annotation wäre das die ID inputSpinner .
Bei der Registrierung des Renderers muss zuerst ein Renderkit ausgewählt werden, für das die Eintragung der zusätzlichen Rendererklasse erfolgt. Die Auswahl ist meistens einfach, und fast immer bleibt es bei der Verwendung des Standard-Renderkits mit der Renderkit-ID HTML_BASIC .
Listing Registrierung einer Rendererklasse zeigt die Registrierung des Beispielrenderers unter der Komponentenfamilie javax.faces.Input und dem Renderertyp at.irian.InputSpinner .
<render-kit>
  <render-kit-id>HTML_BASIC</render-kit-id>
  <renderer>
    <component-family>javax.faces.Input</component-family>
    <renderer-type>at.irian.InputSpinner</renderer-type>
    <renderer-class>
      at.irian.jsfatwork.gui.jsf.component.InputSpinnerRenderer
    </renderer-class>
  </renderer>
</render-kit>
Ein Renderer kann ab JSF-Version 2.0 auch mit der Annotation @FacesRenderer im System registriert werden. Die notwendigen Daten werden in den Elementen renderKitId , componentFamily und rendererType angegeben. Die Renderkit-ID kann auch weggelassen werden und wird dann auf das Standard-Renderkit gesetzt. Listing Registrierung einer Rendererklasse mittels Annotation zeigt den Code.
@FacesRenderer(componentFamily = "javax.faces.Input",
    rendererType = "at.irian.InputSpinner")
public class InputSpinnerRenderer extends Renderer {
  ...
}

6.2.5 Tag-Definition schreiben

Alle bisherigen Schritte, das Erstellen der Komponenten- und Rendererklasse und das Registrieren der beiden Klassen im System, gestalten sich unabhängig von der eingesetzten Seitendeklarationssprache immer gleich. Bei der Definition des Tags der Komponente ist das leider nicht mehr möglich.
Mit JSP ist die Definition des Tags und das damit verbundene Erstellen der Tag-Klasse ein recht mühsames Unterfangen. Da mit Version 2.0 der Spezifikation der Fokus eindeutig auf Facelets gelegt wurde und JSP nur noch eine Nebenrolle spielt, werden wir darauf an dieser Stelle nicht mehr näher eingehen.
Mit Facelets erfordert das Erstellen einer Tag-Definition keinen großen Aufwand. Existiert bereits eine passende Tag-Bibliothek reicht die Angabe des Tag-Namens, des Komponententyps und des Renderertyps, um das Tag zu spezifizieren. Ist das nicht der Fall, muss zuerst eine neue Tag-Bibliothek angelegt und dem System bekannt gemacht werden. Wie das funktioniert, zeigt Abschnitt Sektion:  Tag-Bibliotheken mit Facelets erstellen . Die komplette Definition des Tags für unsere Beispielkomponente zeigt Listing Definition des Tags der Beispielkomponente .
<tag>
  <tag-name>inputSpinner</tag-name>
  <component>
    <component-type>at.irian.InputSpinner</component-type>
    <renderer-type>at.irian.InputSpinner</renderer-type>
  </component>
</tag>
JSF 2.2: Mit JSF 2.2 lässt sich der Aufwand zum Erstellen eines Komponenten-Tags auf ein absolutes Minimum reduzieren. Im einfachsten Fall reicht es aus, das Element createTag von @FacesComponent auf true zu setzen. JSF stellt die Komponente dadurch per Konvention im Namensraum http://xmlns.jcp.org/jsf/component zur Verfügung. Der Name des Tags leitet sich dabei automatisch aus dem Klassenname (mit kleinem Anfangsbuchstaben) ab.
Wenn Sie eigene Werte für den Namensraum oder den Tag-Namen verwenden wollen, müssen Sie diese explizit angeben. Der Tag-Name kann im Element tagName und der Namensraum im Element namespace gesetzt werden. Abbildung Definition des Tags mittels Annotation zeigt ein konkretes Beispiel.
@FacesComponent(value="at.irian.InputSpinner",
    createTag=true, tagName="inputSpinner",
    namespace="http://at.irian/test")
public class InputSpinner extends UIInput {
...
}
Diese Methode eignet sich gut zur schnellen Definition von Tags für einzelne Komponenten. Spätestens wenn Sie mehr als eine Handvoll Komponenten haben, sollten Sie aber eine vollständige Tag-Bibliothek inklusive XML-Konfiguration in Betracht ziehen.
Attribute über Reflection: Woher weiß Facelets, welche Attribute für das Tag verfügbar sind? Über Reflection greift Facelets auf die Komponentenklasse zu und ermittelt die möglichen Attribute aus dieser Klasse.

6.2.6 Tag-Behandlungsklasse schreiben

In seltenen Fällen wird auch für Facelet-Tags eine Behandlungsklasse benötigt. Das ist beispielsweise dann der Fall, wenn hinter dem Tag keine Komponente existiert, wie das für c:if aus der JSTL-Bibliothek von Facelets der Fall ist. Listing Tag-Handler für c:if zeigt den Code dieses Tag-Handlers.
public final class IfHandler extends TagHandler {
  private final TagAttribute test;
  private final TagAttribute var;

  public IfHandler(TagConfig config) {
    super(config);
    this.test = this.getRequiredAttribute("test");
    this.var = this.getAttribute("var");
  }
  public void apply(FaceletContext ctx, UIComponent parent)
      throws IOException, FacesException, ELException {
    boolean b = this.test.getBoolean(ctx);
    if (this.var != null) {
      ctx.setAttribute(var.getValue(ctx), new Boolean(b));
    }
    if (b) this.nextHandler.apply(ctx, parent);
  }
}
Im Konstruktor wird der Tag-Handler mit den Werten aus der Seite initialisiert. Mit dem Aufruf von getRequiredAttribute() wird garantiert, dass das Attribut in der Seitendeklaration gesetzt ist. Ist das nicht der Fall, wirft Facelets eine Exception. Essenziell ist dann die Methode apply - die immer dann aufgerufen wird, wenn das Tag beim Bauen des Komponentenbaums intern aufgerufen wird. Hier findet die eigentliche Logik statt, die im Falle von c:if die Kindkomponenten des Tags abhängig vom Wert des Attributs test in den Komponentenbaum einfügt oder nicht. Da die Kindkomponenten explizit über einen Aufruf von this.nextHandler.apply() abgearbeitet werden, ist es ein Leichtes, diesen Vorgang zu beeinflussen.
Tag-Handler wie der soeben beschriebene werden in der Tag-Bibliothek direkt in einer Tag-Definition verwendet und können so in der Seitendeklaration wie eine Komponente eingesetzt werden. Hier gilt es allerdings, zu beachten, dass ein Tag-Handler nur beim Aufbau des Komponentenbaums aufgerufen wird. Die Definition des Tags sieht in diesem Fall wie folgt aus:
<tag>
  <tag-name>if</tag-name>
  <handler-class>IfHandler</handler-class>
</tag>
Tag-Handler können aber auch für Komponenten verwendet werden, die ein spezielles Verhalten erfordern. Zur Demonstration erstellen wir einen Tag-Handler für unsere Input-Spinner-Komponente, der das Attribut inc verpflichtend macht. Den Sourcecode des Tag-Handlers finden Sie in Listing Tag-Handler für die Beispielkomponente .
public class InputSpinnerTagHandler extends ComponentHandler {
  private TagAttribute inc;
  public InputSpinnerTagHandler(ComponentConfig conf) {
    super(conf);
    this.inc = getRequiredAttribute("inc");
  }
}
Dieser Tag-Handler kann jetzt zusätzlich bei der Definition des Kompo-nenten-Tags angegeben werden. Listing Definition des Tags der Beispielkomponente mit Tag-Handler zeigt die um den Tag-Handler erweiterte Definition.
<tag>
  <tag-name>inputSpinner</tag-name>
  <component>
    <component-type>at.irian.InputSpinner</component-type>
    <renderer-type>at.irian.InputSpinner</renderer-type>
    <handler-class>
      at.irian.jsfatwork.gui.jsf.component.InputSpinnerTagHandler
    </handler-class>
  </component>
</tag>

6.2.7 Tag-Bibliothek einbinden

Zu guter Letzt bleibt nur noch ein Schritt übrig: Bevor die Komponente in einer Deklaration zum Einsatz kommen kann, muss die Tag-Bibliothek eingebunden werden. Wie das funktioniert, haben wir ja bereits bei den Standardkomponenten und diversen anderen Gelegenheiten gezeigt und muss hier nicht mehr wiederholt werden.
An dieser Stelle möchten wir Ihnen noch einmal veranschaulichen, wie JSF beim Aufbau des Komponentenbaums die Komponenten- und Rendererklasse für ein Tag auflöst. Trifft JSF beispielsweise auf das Tag mg:inputSpinner , ist aus der Definition in der Tag-Bibliothek bereits der Komponententyp at.irian.InputSpinner und der Renderertyp at.irian.InputSpinner bekannt. Über den Komponententyp kann jetzt bereits die Komponenteninstanz erstellt und in den Baum eingefügt werden. Mit dieser Information lässt sich auch die Rendererklasse auflösen. Wenn wir davon ausgehen, dass das Standard-Renderkit mit dem Bezeichner HTML_BASIC zum Einsatz kommt, kann mit dem Renderertyp und der Komponentenfamilie die Klasse des Renderers aus der Konfiguration bestimmt werden. Die Komponentenfamilie ist ja jederzeit über einen Aufruf der Methode getFamily() der Komponente abrufbar.
Das ist alles, was man über das Schreiben von eigenen Komponenten wissen muss - viel Erfolg beim Erstellen der dynamischsten, interaktivsten und schönsten JSF-Komponenten!

6.3 Kompositkomponenten und klassische Komponenten kombinieren

Wir haben Ihnen in den letzten beiden Abschnitten die Entwicklung von Kompositkomponenten und klassischen Komponenten nähergebracht. In diesem Abschnitt werden wir Ihnen zeigen, dass sich diese Konzepte nicht gegenseitig ausschließen, sondern ganz im Gegenteil sogar sehr gut harmonieren.
Bei der Entwicklung von Kompositkomponenten tritt immer wieder der Fall ein, dass ein gewünschtes Verhalten nur mit Java-Code realisierbar ist. Wir benötigen also einen Erweiterungspunkt zur Integration dieses Codes. Wie wir Ihnen bereits gezeigt haben, sind Kompositkomponenten intern aus klassischen Komponenten aufgebaut. Der naheliegendste Gedanke ist daher, diesen Java-Code in Form einer klassischen Komponente zu realisieren.
JSF verfolgt exakt diesen Gedanken und erlaubt bei Kompositkomponenten die freie Wahl des Typs der Wurzelkomponente. Über das Attribut componentType im Element cc:interface kann der Komponententyp dieser Komponente explizit angegeben werden. Die eingesetzte Komponentenklasse muss als einzige Voraussetzung das Interface implementieren und in getFamily() die Komponentenfamilie javax.faces.NamingContainer zurückliefern. Wird component-Type nicht gesetzt, erzeugt JSF automatisch eine Komponente vom Typ .
Genau das werden wir jetzt für die in Abschnitt [Sektion:  Die Komponente mc:collapsiblePanel] vorgestellte Kompositkomponente collapsiblePanel machen. Dort haben wir ja bereits kritisch angemerkt, dass Benutzer der Komponente die Logik zum Ein- und Ausblenden selbst bereitstellen müssen. Wir werden diese Funktionalität in der Komponente CollapsiblePanel umsetzen, die wir dann mit der Kompositkomponente verknüpfen. Die Komponente selbst kann dabei sehr einfach gehalten werden. Sie muss lediglich über die Eigenschaft collapsed und eine Ereignisbehandlungsmethode zum Ein- und Ausblenden verfügen. Listing Komponente CollapsiblePanel zeigt die Komponentenklasse, die mit @FacesComponent unter dem Komponententyp at.irian.CollapsiblePanel registriert wird.
@FacesComponent("at.irian.CollapsiblePanel")
public class CollapsiblePanel extends UINamingContainer {
  enum PropertyKeys {collapsed}

  public boolean isCollapsed() {
    return (Boolean)getStateHelper().eval(
        PropertyKeys.collapsed, Boolean.FALSE);
  }
  public void setCollapsed(boolean collapsed) {
    getStateHelper().put(PropertyKeys.collapsed, collapsed);
  }
  public void toggle(ActionEvent e) {
    setCollapsed(!isCollapsed());
    setCollapsedValueExpression();
  }
  private void setCollapsedValueExpression() {
    ELContext ctx = FacesContext.getCurrentInstance()
        .getELContext();
    ValueExpression ve = getValueExpression(
        PropertyKeys.collapsed.name());
    if (ve != null) ve.setValue(ctx, isCollapsed());
  }
}
Da die Komponentenklasse von UINamingContainer abgeleitet ist, brauchen wir keine weiteren Vorkehrungen treffen und können sie direkt in der Kompositkomponente verwenden. Ist Ihre Komponente von einer anderen Klasse wie etwa UIInput abgeleitet, muss sie zusätzlich das Interface NamingContainer implementieren und in getFamily() den Wert javax.faces.NamingContainer zurückliefern. Der Wert der Eigenschaft collapsed wird intern mit dem bereits bekannten StateHelper verwaltet. Ein Aufruf der Ereignisbehandlungsmethode toggle ändert lediglich den Wert dieser Eigenschaft. Hat der Benutzer der Kompositkomponente im Attribut collapsed eine Value-Expression angegeben, wird mit der Methode setCollapsedValueExpression() zusätzlich der aktuelle Einklappzustand zurückgeschrieben.
Wenden wir uns nun der Kompositkomponente selbst zu. Die Interna ändern sich im Vergleich zu Abschnitt [Sektion:  Die Komponente mc:collapsiblePanel] nur minimal und werden etwas anders verdrahtet. Listing Kompositkomponente collapsiblePanelmit benutzerdefinierter Wurzelkomponente zeigt die aktualisierte Komponente collapsiblePanel mit gesetzem componentType -Attribut.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://xmlns.jcp.org/jsf/html"
    xmlns:cc="http://xmlns.jcp.org/jsf/composite"
    xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
<cc:interface componentType="at.irian.CollapsiblePanel">
  <cc:attribute name="collapsed"/>
  <cc:actionSource name="toggle"/>
  <cc:facet name="header"/>
</cc:interface>
<cc:implementation>
  <h:panelGroup layout="block"
      styleClass="collapsiblePanel-header">
    <h:commandButton actionListener="#{cc.toggle}"
        id="toggle" styleClass="collapsiblePanel-img"
        image="#{resource[cc.collapsed 
          ? 'mygourmet:toggle-plus.png'
          : 'mygourmet:toggle-minus.png']}"/>
    <cc:renderFacet name="header"/>
  </h:panelGroup>
  <h:panelGroup layout="block" rendered="#{!cc.collapsed}">
    <cc:insertChildren/>
  </h:panelGroup>
</cc:implementation>
</ui:composition>
Als erster Schritt wird das verpflichtende Attribut model durch das optionale Attribut collapsed ersetzt. Damit kann der Einklappzustand von außen über eine Value-Expression mit einer Bean-Eigenschaft verknüpft werden. Das eröffnet dem Benutzer die Möglichkeit, einen initialen Wert zu setzen und den aktuellen Einklappzustand abzuspeichern. Das Auswerten des initialen Zustands aus dem Attribut collapsed wird dabei intern automatisch vom StateHelper der Komponente erledigt.
Die zweite Änderung betrifft die Ereignisbehandlungsmethode toggle und die Eigenschaft collapsed . Da beide jetzt direkt von der Wurzelkomponente zur Verfügung gestellt werden, ändern sich die EL-Ausdrücke für den Zugriff auf cc.toggle und cc.collapsed . Das ist möglich, da die mit cc referenzierte Komponente jetzt eine Instanz der zuvor erstellten Klasse CollapsiblePanel ist.
Damit ist die verbesserte Version der Kompositkomponente collapsiblePanel auch schon einsatzbereit. Jetzt können wir tatsächlich von einem eigenständigen und wiederverwendbaren Baustein sprechen. Der nächste logische Schritt wäre jetzt, die Komponente inklusive aller als Jar-Datei zur Verfügung zu stellen. Abschnitt [Sektion:  Die eigene Komponentenbibliothek] zeigt, wie das funktioniert.

6.4 Alternativen zur eigenen Komponente

Eine Komponente besteht aus den Teilen Komponentenklasse, Rendererklasse und einem optionalen Tag-Handler. Alle diese Teile sind miteinander verbunden, können aber auch getrennt voneinander ausgetauscht werden. Die einfachste Möglichkeit, eine Komponente zu verändern, ohne eine neue Komponente schreiben zu müssen, ist der Austausch der Rendererklasse.

6.4.1 Austausch der Rendererklasse

Um die Rendererklasse auszutauschen, muss zuerst die Konfiguration in der faces-config.xml verändert werden. Listing Konfiguration eines Renderers zeigt ein Beispiel.
<render-kit>
  <render-kit-id>HTML_BASIC</render-kit-id>
  <renderer>
    <component-family>javax.faces.Output</component-family>
    <renderer-type>javax.faces.Label</renderer-type>
    <renderer-class>
      mypackage.RequiredLabelRenderer
    </renderer-class>
  </renderer>
</render-kit>
Beispiel: RequiredLabel: Hier wird der Renderer für die Label -Komponente durch die Klasse mypackage.RequiredLabelRenderer überschrieben. Jetzt bleibt nur noch, diese Klasse zu implementieren. Listing Rendern eines Labels mit Stern für Pflichtfelder zeigt eine Implementierung, in der das required -Attribut der zum Label gehörenden Komponente ausgewertet wird.
public class RequiredLabelRenderer extends HtmlLabelRenderer {
  protected void encodeBeforeEnd(FacesContext facesContext,
      ResponseWriter writer, UIComponent uiComponent)
      throws IOException {
    String forAttr = getFor(uiComponent);
    if (forAttr != null) {
      UIComponent forComponent =
          uiComponent.findComponent(forAttr);
      if (forComponent instanceof UIInput &&
          ((UIInput) forComponent).isRequired()) {
        writer.startElement(HTML.SPAN_ELEM, null);
        writer.writeAttribute(HTML.ID_ATTR,
            uiComponent.getClientId(facesContext)+
            "RequiredLabel", null);
        writer.writeAttribute(HTML.CLASS_ATTR,
            "requiredLabel", null);
        writer.writeText("*", null);
        writer.endElement(HTML.SPAN_ELEM);
      }
    }
  }
}
Je nach dem Wert dieses Attributs wird ein Stern an die Label-Beschreibung angefügt. Zu diesem Zweck wird der Pflichtfeldrenderer von HtmlLabelRenderer abgeleitet und die Methode encodeBeforeEnd() der Basisklasse überschrieben. In dieser Methode wird zuerst die zum Label gehörende Komponente gesucht; anschließend wird das -Attribut dieser Komponente abgefragt. Gehört die Komponente zu einem Pflichtfeld, wird ein span -Tag mit einer CSS-Klasse und einem * als Inhalt ausgegeben. Sehr einfach und sehr effektiv! Beachten Sie aber bitte, dass die Klasse HtmlLabelRenderer aus Apache MyFaces stammt und nicht im Standard enthalten ist. Nichtsdestotrotz ändert sich, auch wenn Sie Mojarra einsetzen, an der grundlegenden Funktionalität nichts.

6.4.2 Austausch der Komponentenklasse

Genauso wie der Austausch der Rendererklasse ist auch der Austausch der Komponentenklasse möglich - in der faces-config.xml -Datei ist ein zusätzlicher Eintrag wie folgt vorzunehmen:
<component>
  <component-type>javax.faces.HtmlInputText</component-type>
  <component-class>
    mypackage.SpecialHtmlInputText
  </component-class>
</component>
Auto-Converter: Mit dieser Vorgehensweise kann beispielsweise automatisch ein Konverter mit der Komponente verbunden werden, ohne dass dazu ein eigenes Konverter-Tag verwendet wird. Ein Beispiel für eine solche Klasse:
public class SpecialHtmlInputText extends HtmlInputText {
  public SpecialHtmlInputText() {
    super();
    setConverter(ConverterFactory.getSpecialConverter());
  }
}
Damit kommt dieser Spezialkonverter für alle Elemente dieser Komponenten zum Einsatz!

6.4.3 Benutzerdefinierte Komponente aus den Backing-Beans-- Component-Binding

Die beiden bisher verwendeten Tricks gelten für alle Elemente eines Komponententyps - was ist zu tun, wenn man nur einzelne Komponenten mit diesem Spezialverhalten auszeichnen möchte und andere nicht?
Beispiel: Zeilenumbruch: Ein Beispiel aus der Praxis: Für eine Applikation wurde eine Verbindung zu einer auf einem AS400-Server laufenden Legacy-Datenbank entwickelt. Die vom Server zurückgegebenen Daten waren mit einem r zur Markierung des Zeilenumbruchs ausgezeichnet - auf dem Frontend sollte diese Markierung ebenfalls zu einem Zeilenumbruch im HTML-Markup führen, musste also als <br/> ausgegeben werden. Da nicht alle Textausgaben geparst werden sollten, wurde das Rendering-Verhalten nur für einen Teil der Komponenten ersetzt.
Um diese Ersetzung vorzunehmen, kann man entweder ein eigenes Tag erstellen und über dieses Tag einen neuen Renderer für die Komponente festlegen, man kann aber auch Component-Binding einsetzen, wobei dieser zweite Weg wesentlich einfacher ist. Dazu sind zuerst alle Komponenten, die ein spezielles Rendering-Verhalten aufweisen sollen, mit einem binding -Attribut zu versehen. Im nächsten Schritt wird dieses Attribut mit der dahinterliegenden Geschäftslogik verbunden. Das folgende Beispiel zeigt einen Ausschnitt aus einer solcherart veränderten Seitendeklaration:
<h:outputText value="#{limitDetail.limitView.comment}"
    binding="#{componentBean.outputWithBreaks}"/>
Die referenzierte Methode sieht dabei so aus:
UIComponent getOutputWithBreaks() {
  return new OutputTextWithBreaks();
}
Jetzt benötigen wir nur noch eine Implementierung dieser Komponente - in Listing Verändern des Rendering-Verhaltens einer Komponente durch Component-10mmBinding wird diese gezeigt. Es wird die encodeEnd() -Methode überschrieben - wo ja üblicherweise ein Renderer für die Komponente gesucht und dessen encodeEnd() -Methode aufgerufen wird. In diesem Fall erledigen wir das Rendering gleich selbst in der Komponente. Das eigentliche Rendering ist in der Abbildung ausgeblendet, da es exakt der Funktionalität in der Rendererklasse entsprechen soll.
public static final class OutputText extends HtmlOutputText {
  public OutputText() {
    super();
  }
  public void encodeEnd(FacesContext context)
      throws IOException {
    String text = RendererUtils.getStringValue(context, this);
    text = HTMLEncoder.encode(text, true, true);
    text = text.replaceAll("\r","<br/>");
    renderOutputText(context, this, text, false);
  }
  public static void renderOutputText(
      FacesContext ctx, UIComponent component,
      String text, boolean escape)
      throws IOException {
    ...
  }
}

6.5 MyGourmet 13: Komponenten und Services

Das Beispiel MyGourmet 13 integriert alle in diesem Kapitel entwickelten Komponenten - sowohl die Kompositkomponenten aus Abschnitt [Sektion:  Kompositkomponenten] als auch die klassische Komponente aus Abschnitt [Sektion:  Klassische Komponenten] und deren Kombination aus Abschnitt [Sektion:  Kompositkomponenten und klassische Komponenten kombinieren] . Neben den vielen neuen Komponenten gibt es noch die neue Ansicht editProvider.xhtml zum Bearbeiten eines Anbieters. Im Zuge dieser Änderung haben wir die Architektur der Anwendung ein klein wenig optimiert und eine Serviceklasse für Objekte vom Typ Provider eingeführt. Listing MyGourmet 13: ProviderService zeigt das Interface ProviderService der Serviceklasse.
public interface ProviderService {
  Provider createNew();
  boolean save(Provider entity);
  void delete(Provider entity);
  List<Provider> findAll();
  Provider findById(long id);
}
Der Grund für diese Optimierung ist schnell erklärt. Nachdem die Managed-Bean ProviderBean im View-Scope abgelegt ist, wird sie für jede Ansicht neu erstellt. Das bedeutet aber auch, dass die Liste der Anbieter jedes Mal neu initialisiert wird. Im letzten Beispiel war das noch kein Problem, da die Anbieterdaten nicht veränderbar waren. Mit der neuen Ansicht editProvider.xhtml wird es allerdings zum Problem, da die vom Benutzer veränderten Daten beim Verlassen der Seite verloren gehen - sie sind ja nur in der Bean gespeichert. Mit einem Service im Application-Scope wird quasi eine Datenbank simuliert und das Problem tritt nicht mehr auf. Als zusätzlicher Vorteil macht eine bereits existierende Serviceklasse einen Umstieg auf eine echte Datenbank sehr einfach. Mit dieser Änderung ist es auch ohne Weiteres möglich, die für die Ansicht providerList.xhtml benötigte Funktionalität in die Managed-Bean ProviderListBean im Request-Scope auszulagern.
Die Klasse ProviderServiceImpl implementiert das Interface ProviderService und stellt den eigentlichen Service dar. Sie steht als Managed-Bean unter dem Namen providerService im Application-Scope zur Verfügung. Listing MyGourmet 13: Implementierung des Service zeigt den Rumpf der Klasse mit den Annotationen. Die Implementierung ist sehr einfach gehalten und basiert intern auf einer Liste, die beim Erzeugen der Bean mit drei Objekten vom Typ Provider initialisiert wird.
@ManagedBean(name = "providerService")
@ApplicationScoped
public class ProviderServiceImpl implements ProviderService {
  ...
}
Viel interessanter ist da schon das automatische Setzen des Service in der Managed-Bean ProviderBean über eine Managed-Property. Listing MyGourmet 13: Setzen des Service in ProviderBean zeigt das entsprechende Codefragment. Sie müssen sich in dem Fall keine Gedanken mehr über den Service machen. Nach dem Erstellen einer Managed-Bean vom Typ ProviderBean setzt JSF den Service automatisch - und das garantiert vor dem ersten Zugriff des Benutzers.
@ManagedProperty(value = "#{providerService}")
private ProviderService providerService;
public void setProviderService(
    ProviderService providerService) {
  this.providerService = providerService;
}
Aber jetzt zurück zum eigentlichen Thema dieses Kapitels: Komponenten. Die Liste der zur Verfügung stehenden Kompositkomponenten umfasst mc:panelBox , mc:dataTable , mc:collapsiblePanel und mc:inputSpinner . Das Präfix mc steht dabei für den Namensraum http://at.irian/mygourmet der MyGourmet-Tag-Bibliothek. Abbildung MyGourmet 13: Ressourcen der Bibliothek mygourmet zeigt den Inhalt der Ressourcenbibliothek mygourmet mit allen Kompositkomponenten, Stylesheets, Bildern und Skripten.
Abbildung:MyGourmet 13: Ressourcen der Bibliothek mygourmet
Die Komponente mg:inputSpinner , also die klassische Variante unserer Input-Spinner-Komponente, kann in gleicher Weise wie die Kompositkomponente eingesetzt werden.

6.6 Die eigene Komponentenbibliothek

In diesem Abschnitt zeigen wir, wie einfach das Erstellen einer eigenen Komponentenbibliothek mit JSF 2.0 geworden ist. Dazu packen wir exemplarisch die Kompositkomponente collapsiblePanel aus Abschnitt [Sektion:  Kompositkomponenten und klassische Komponenten kombinieren] inklusive aller benötigten Artefakte in eine Jar-Datei. Nachdem wir die Komponente selbst bereits wiederverwendbar gemacht haben, kann diese Jar-Datei in jeder JSF-Anwendung eingesetzt werden.
Eine erste Version unserer Komponentenbibliothek ist schnell erstellt. Wir müssen lediglich das Verzeichnis der Ressourcenbibliothek mygourmet und die Klasse CollapsiblePanel der benutzerdefinierten Wurzelkomponente in die Jar-Datei aufnehmen.
Da die Komponente CollapsiblePanel mit der Annotation @FacesComponent im System registriert wird, brauchen wir eigentlich keine XML-Konfiguration. Im Endeffekt müssen wir aber doch eine leere faces-config.xml anlegen. JSF berücksichtigt Annotationen nur in jenen Jar-Dateien, die eine Datei mit dem Namen faces-config.xml oder mit der Endung .faces-config.xml im Verzeichnis META-INF beinhalten. Listing faces-config.xml für die Komponentenbibliothek zeigt die leere faces-config.xml für JSF 2.2.
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
    version="2.2">
</faces-config>
Damit JSF die Ressourcen unserer Bibliothek mygourmet aus der Jar-Dateien im Classpath auflösen kann, muss sie im Verzeichnis META-INF/resources liegen. Die Komponentenbibliothek ist damit einsatzbereit und kann in jeder beliebigen JSF-Anwendung verwendet werden, sobald das Jar-Archiv im Classpath verfügbar ist. An der Verwendung der Kompositkomponente collapsiblePanel hat sich dabei nichts geändert. Sie wird wie bisher über den Namensraum http://xmlns.jcp.org/jsf/composite/mygourmet und den Tag-Namen collapsiblePanel eingebunden.
Diese zugegeben sehr einfache Komponentenbibliothek bietet sich als Basis für Erweiterungen an. Sie kann neben weiteren Kompositkomponenten auch mit klassischen Komponenten, Konvertern und Validatoren ergänzt werden. Die Tags für diese Artefakte müssen allerdings in einer Tag-Bibliothek konfiguriert werden (siehe Abschnitt Sektion:  Tag-Bibliotheken mit Facelets erstellen ). Für unsere Beispiel erstellen wir dazu im Verzeichnis META-INF die Tag-Bibliothek mygourmet.taglib.xml mit dem Namensraum http://at.irian/mygourmet .
Die Tags der Kompositkomponenten und die Tags der Tag-Bibliothek sind dann allerdings über unterschiedliche Namensräume erreichbar. Um das zu vermeiden, erlaubt JSF das Importieren von Kompositkomponenten in Tag-Bibliotheken. Dazu muss im Element composite-library-name der Name der Bibliothek - in unserem Fall mygourmet - angegeben werden. Dieser Ansatz funktioniert zwar, es lässt sich aber immer nur eine komplette Ressourcenbibliothek pro Tag-Bibliothek einbinden.
JSF 2.2: JSF 2.2 schafft hier Abhilfe und ermöglicht die Definition von Tags für einzelne Kompositkomponenten aus unterschiedlichen Bibliotheken. Dazu muss in der Tag-Bibliothek ein Tag für eine Komponente mit einer Ressourcen-ID im Element resource-id hinzugefügt werden. Diese Ressourcen-ID besteht aus dem Bibliotheksnamen und dem durch einen Schrägstrich ( / ) getrennten Ressourcennamen der Kompositkomponente. Listing Konfiguration der Tag-Bibliothek für die Komponenten-bibliothek zeigt die Konfiguration des Tags für die Kompositkomponente collapsiblePanel aus unserer Tag-Bibliothek.
<facelet-taglib version="2.2"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/
      web-facelettaglibrary_2_2.xsd">
  <namespace>http://at.irian/mygourmet</namespace>
  <tag>
    <tag-name>collapsiblePanel</tag-name>
    <component>
      <resource-id>mygourmet/collapsiblePanel.xhtml</resource-id>
    </component>
  </tag>
</facelet-taglib>
JSF bindet Tag-Bibliotheken aus Jar-Dateien im Classpath automatisch ein - allerdings nur wenn eine Konfigurationsdatei im Verzeichnis META-INF liegt, deren Namen mit der Erweiterung .taglib.xml endet. Das Tag collapsiblePanel ist jetzt in Applikationen unter dem Namensraum http://at.irian/mygourmet verfügbar.
Abbildung Struktur der eigenen Komponenten-bibliothek zeigt abschließend noch die Struktur und den Inhalt der Jar-Datei für unser Beispiel.
Abbildung:Struktur der eigenen Komponenten-bibliothek
In Abschnitt [Sektion:  MyGourmet 13 mit Komponentenbibliothek] finden Sie nochmals das Beispiel MyGourmet 13 - diesmal allerdings mit einer Tag-Bibliothek, die alle Kompositkomponenten, Komponenten, Validatoren und Konverter in einer eigenen Jar-Datei zusammenfasst.

6.7 MyGourmet 13 mit Komponentenbibliothek

In diesem Abschnitt finden Sie eine kurze Beschreibung zu einer alternativen Version von MyGourmet 13 . In dieser Version sind die Kompositkomponenten und alle Bestandteile der eingesetzten Tag-Bibliothek in einer Komponentenbibliothek zusammengefasst.
Diese Komponentenbibliothek beinhaltet zum einen im Verzeichnis META-INF/resources die Ressourcenbibliothek mygourmet und zum anderen die Tag-Bibliothek mygourmet.taglib.xml im Verzeichnis META-INF . Dazu kommt noch eine Reihe von Java-Klassen wie Komponenten- und Rendererklassen, Konverter und Validatoren. Die Kompositkomponenten sind, wie im letzten Abschnitt beschrieben, in die Tag-Bibliothek importiert. Damit stehen alle Artefakte unter dem Namensraum http://at.irian/mygourmet zur Verfügung.
Auf der Ebene des Quellcodes ist die Komponentenbibliothek als Maven-Modul realisiert. Die Projektbeschreibung pom.xml des Beispiels MyGourmet 13 mit Komponentenbibliothek umfasst zwei Module: mygourmet13-taglib enthält alle Bestandteile der Komponentenbibliothek und mygourmet13-webapp beinhaltet den Rest der Applikation. Die Verbindung zwischen den beiden Modulen ist in mygourmet13-webapp über eine Abhängigkeit auf mygourmet13-taglib definiert.
Um die Applikation zu starten, müssen Sie mvn jetty:run im Verzeichnis des Moduls mygourmet13-webapp aufrufen. Zuvor muss aber das Modul mygourmet13-taglib oder das ganze Projekt mygourmet13 gebaut werden. Das Erstellen von mygourmet13-taglib liefert als Ergebnis eine Jar-Datei mit allen Teilen der Komponentenbibliothek.