9 Trinidad - Die Komponentenbibliothek
9.1 Trinidad - Ein Überblick
Trinidad wurde ursprünglich von Oracle unter dem Namen ADF Faces entwickelt und stellt eine umfangreiche Komponentenbibliothek für JSF dar. Später wurde die Komponentenbibliothek der Apache Software Foundation (ASF) gespendet. Genauer gesagt wurden ADF Faces zu einem Bestandteil des MyFaces Projekts. Unter dem neuen Namen Trinidad wurde die Komponentenbibliothek bis Mai 2007 im Incubator bearbeitet. Aktuell werden wie bei MyFaces Core zwei Releases geführt. Trinidad 1.0.x für MyFaces 1.1.x und Trinidad 1.2.x für MyFaces 1.2.x. Weitere Informationen bezüglich Kompatibilität von und rund um MyFaces werden auf http://myfaces.apache.org/compatibility.html bereitgestellt.Warum Trinidad?
Einer der Vorteile von JSF ist, dass es mittlerweile eine Vielzahl an Komponentenbibliothek am Markt gibt. Neben kommerziellen Produkten gibt es auch mehrere Open-Source Projekte. MyFaces selbst umfasst Tomahawk, Tobago und Trinidad. Zusätzlich zu einer großen Anzahl an Komponenten mit umfangreichen Funktionalitäten bietet Trinidad durchgängige und fortschrittliche Konzepte. So unterstützen Trinidad-Komponenten unter anderem Ajax (auch Partial Page Rendering genannt), Skinning und clientseitige Validierung. Außerdem bietet Trinidad out-of-the-box Lösungen für die Request- vs. Session-Scope Thematik, effizienteres client-side state saving und transparente Unterstützung für Facelets. Nicht zu vergessen ist, dass Trinidad unter der Apache 2.0 License geführt wird und somit ein Open-Source Produkt ist, welches bedenkenlos in kommerziellen (Closed-Source) Projekten eingesetzt werden kann. Zusätzlich profitiert Trinidad von der professionellen Community, welche hinter dem MyFaces Projekt steht. Neben Dokumentation, Wikis, diversen Artikeln und Demo-Applikationen kann über die User-Mailing-Liste (users@myfaces.apache.org) auf fundierte Hilfe durch die MyFaces Community zurückgegriffen werden. Zusätzlich zu der Weiterentwicklung durch die MyFaces Community profitiert Trinidad zusätzlich durch die Unterstützung von Oracle. Zusätzlich stellt beispielsweise irian.at ( http://www.irian.at/ ) kompetente Unterstützung für den Einsatz von Trinidad in JSF-Projekten bereit. Detaillierte Informationen über die umfangreichen Angebote rund um Trinidad sind auf http://www.irian.at/adfFaces ersichtlich.
9.2 Installation und Konfiguration
Die Installation von Trinidad geht ähnlich leicht von der Hand, wie die von JSF selbst.9.2.1 Dependencies
Der erste Schritt ist die Aufnahme der beiden Trinidad-Jar-Archive (trinidad-api und trinidad-impl) in den Classpath. Abbildung Ausschnitt aus den Dependencies einer Maven2 pom.xml Datei zeigt die erforderlichen Dependencies für die Konfiguration von Trinidad in Version 1.0.4 im pom.xml von Maven 2. <dependency>
<groupId>org.apache.myfaces.trinidad</groupId>
<artifactId>trinidad-api</artifactId>
<version>1.0.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.myfaces.trinidad</groupId>
<artifactId>trinidad-impl</artifactId>
<version>1.0.4</version>
<scope>compile</scope>
</dependency>
Abbildung: Ausschnitt aus den Dependencies einer Maven2 pom.xml Datei
9.2.2 Konfiguration
9.2.2.1 web.xml - Grundkonfiguration
Um Trinidad erfolgreich zu aktivieren ist die Angabe des ResourceServlets inklusive Servlet-Mapping, sowie eines Filters erforderlich. Weiters ist ein Filter-Mapping zwischen dem TrinidadFilter und dem JSF-Servlet erforderlich. Unabhängig von weiteren Trinidad-Features ist die Grundkonfiguration aus Abbildung Grundkonfiguration von Trinidad in der web.xml immer erforderlich. <servlet>
<servlet-name>resources</servlet-name>
<servlet-class>org.apache.myfaces.trinidad.webapp.ResourceServlet</servlet-class>
</servlet>
<filter>
<filter-name>trinidad</filter-name>
<filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>trinidad</filter-name>
<servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
<servlet-mapping>
<servlet-name>resources</servlet-name>
<url-pattern>/adf/*</url-pattern>
</servlet-mapping>
Abbildung: Grundkonfiguration von Trinidad in der web.xml
Schlussendlich muss der Render-Kit von Trinidad als Default-Render-Kit eingetragen werden (Abbildung Ausschnitt aus der faces-config.xml für die Konfiguration von Trinidad ).
<application>
<default-render-kit-id>org.apache.myfaces.trinidad.core</default-render-kit-id>
</application>
Abbildung: Ausschnitt aus der faces-config.xml für die Konfiguration von Trinidad
9.2.2.2 Weitere web.xml Kontextparameter
Wird wie in der Beispielapplikation Facelets verwendet, so ist ein weiterer Parameter erforderlich. Durch den Parameter in Abbildung Ausschnitt aus der web.xml für die Verbindung von Trinidad und Facelets wird festgelegt, dass spezifische Implementierungen für Facelets innerhalb von Trinidad aktiv werden. <context-param>
<param-name>org.apache.myfaces.trinidad.ALTERNATE_VIEW_HANDLER</param-name>
<param-value>com.sun.facelets.FaceletViewHandler</param-value>
</context-param>
Abbildung: Ausschnitt aus der web.xml für die Verbindung von Trinidad und Facelets
Neben dem alternativen View-Handler gibt es für die Konfiguration von Trinidad folgende Kontextparameter:
org.apache.myfaces.trinidad.CACHE_VIEW_ROOT
org.apache.myfaces.trinidad.CLIENT_STATE_METHOD
org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS
org.apache.myfaces.trinidad.USE_APPLICATION_VIEW_CACHE
org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT
org.apache.myfaces.trinidad.DISABLE_CONTENT_COMPRESSION
org.apache.myfaces.trinidad.CHECK_FILE_MODIFICATION
org.apache.myfaces.trinidad.ENABLE_LIGHTWEIGHT_DIALOGS
org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
org.apache.myfaces.trinidad.DIALOG_NAVIGATION_PREFIX
org.apache.myfaces.trinidad.DISABLE_DIALOG_OUTCOMES
Abbildung: Liste der web.xml Kontextparameter für Trinidad
org.apache.myfaces.trinidad.CACHE_VIEW_ROOT
Default-Wert: true
Dieser Parameter ermöglicht die De-/Aktivierung der Caching Funktionalität für View Roots. D.h., dass ein Postback schneller abgearbeitet werden kann, weil der ViewRoot nicht erneut aufgebaut werden muss. Dies ist vor allem bei Ajax intensiven Applikationen eine gute Möglichkeit, um Performance zu optimieren. Allerdings ist zu beachten, dass andere JSF Erweiterungen wie bspw. t:saveState aus der Tomahawk Bibliothek, sowie bestimmte Seiten nicht mehr funktionieren. Ist dies der Fall und es ergeben sich unerklärliche Phänomene, die ohne Trinidad nicht auftreten, dann könnte es an der Aktivierung dieses Features liegen. Somit bleibt hier nur noch die Deaktivierung dieser Funktionalität.
org.apache.myfaces.trinidad.CLIENT_STATE_METHOD
Default: token
Mögliche Werte: token, all
Unter Voraussetzung, dass client-side state saving aktiviert ist (siehe javax.faces.STATE_SAVING_METHOD ) kann mit diesem Parameter beeinflusst werden, ob nur ein Token ( " token" ) oder der serialisierte State ( " all" ) auf der Clientseite verwendet wird. Wird der Default Wert " token" verwendet, so wird der State in der Session gehalten und ein Token zur Identifikation des entsrpechenden States am Client gespeichert. Außerdem wird Failover unterstützt. Mit " all" kommt das Standard-JSF-Vorgehen zum Einsatz, bei welchem der State in einem Hidden-Field serialisiert abgelegt wird. Der Vorteil ist, dass die HttpSession nicht belastet wird. Allerdings müssen somit mehr Daten an den Client geschickt werden.
org.apache.myfaces.trinidad.CLIENT_STATE_MAX_TOKENS
Default: 15
Mögliche Werte: gültige Integer Zahlen
Wenn bei org.apache.myfaces.trinidad.CLIENT_STATE_METHOD der Default-Wert " token" zum Einsatz kommt, dann kann mit diesem Parameter festgelegt werden, wie viele Tokens zur Verfügung stehen, bevor der älteste gelöscht wird und somit der entspreche State nicht mehr gecached wird. Hierbei sind zwei Nebeneffekte zu beachten. Einerseits muss eine sinnvolle Zahl gewählt werden, wenn der Back-Button des Browsers, oder mehrere Browserfenster unterstützt werden soll. Außerdem ist eine entsprechend hohe Zahl anzugeben, wenn Seiten mit Frames zum Einsatz kommen sollen.
org.apache.myfaces.trinidad.USE_APPLICATION_VIEW_CACHE
Default: false
Wenn bei org.apache.myfaces.trinidad.CLIENT_STATE_METHOD der Default-Wert " token" zum Einsatz kommt ist hier eine Performancesteigerung möglich, da durch die Aktivierung dieser Funktionalität Seiten gecached werden. Für die Entwicklung ergibt sich somit der Nachteil, dass geänderte Seiten einen Neustart der Webapplikation erfordern. Der Vorteil dieses Features ist eine höhere Skalierbarkeit.
org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT
Default: false
Wird dieses Feature aktiviert, so wird JavaScript nicht obfuscated und sämtliche Kommentare werden ebenfalls an den Client gesendet. Somit dient dieses Feature, wie bereits der Name vermuten lässt, dem komfortableren Debugging von JavaScript zur Entwicklungszeit.
org.apache.myfaces.trinidad.DISABLE_CONTENT_COMPRESSION
Default: true
Per Default werden die Namen der CSS Style-Klassen für Skinning durch Trinidad komprimiert. Somit müssen weniger Daten übertragen werden. Allerdings sind die Style-Klassen nicht mehr ohne weiteres lesbar. Um die ursprünglichen Namen zu verwenden kann dieses Feature deaktiviert werden. Dies kann primär bei der Entwicklung hilfreich sein.
org.apache.myfaces.trinidad.CHECK_FILE_MODIFICATION
Default: false
Dieses Parameter ist ebenfalls für die Entwicklung gedacht und ermöglicht, dass jede geänderte JSP neu verarbeitet wird. Außerdem werden entsprechende States (saved states) verworfen.
org.apache.myfaces.trinidad.ENABLE_LIGHTWEIGHT_DIALOGS
Default: false
Wird dieses Feature aktiviert, so werden Dialoge nicht in einem neuen Browser Fenster geöffnet, sondern es wird ein iframe über die aktuelle Seite gelegt. In diesem iframe wird die entsprechende Dialogseite angezeigt. Innerhalb des iframes ist wie gewohnt Skinning möglich.
org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
Default: -
Mögliche Werte: session oder Klassenname
Wird kein Werte gesetzt, so wird org.apache.myfaces.trinidadinternal.change.NullChangeManager verwendet. Wird Session angegeben, so wird org.apache.myfaces.trinidadinternal.change.SessionChangeManager verwendet. Hierbei wird als Persistenzstrategie die HttpSession verwendet. Durch die Angabe eines voll qualifizierten Klassennames kann hier eine eigene Implementierung des ChangeManagers angegeben werden.
org.apache.myfaces.trinidad.DIALOG_NAVIGATION_PREFIX
Default: dialog:
Im Kapitel über Trinidad Dialoge wird das Dialog-Feature im Detail erklärt. Mit diesem Parameter kann der Prefix zum Start von Dialogen geändert werden.
org.apache.myfaces.trinidad.DISABLE_DIALOG_OUTCOMES
Default: false
Mit diesem Parameter können Dialog zentral deaktiviert werden, selbst wenn in einer/mehreren Seite(n) ein gültiger Dialog-Prefix zum Einsatz kommt.
Dieses Parameter dient lediglich für Tests von Rendererkits und bewirkt, dass keine Bilder erzeugt/gerendert werden.
9.2.2.3 trinidad-config.xml
Zusätzlich zu der Konfiguration in der web.xml sind weitere Konfigurationen möglich, welche in der Datei trinidad-config.xml angegeben werden können. Damit Trinidad auf diese Konfigurationsdatei zugreifen kann, muss diese im WEB-INF Verzeichnis der Webapplikation angelegt werden. Ein wichtiger Vorteil dieser XML basierten Konfiguration ist die optionale Verwendung von EL-Ausdrücken direkt in den Konfigurationseinträgen. Jeder Eintrag kann fix oder per EL-Ausdruck definiert werden. Erfolgt eine Angabe per EL-Ausdruck, so wird diese bei jedem Request ausgewertet.Abbildung Beispiel für einen Ausschnitt aus trinidad-config.xml zeigt beide Möglichkeiten. Der Debugging Modus ist hier statisch angegeben. Im Gegensatz hierzu wird Skinning per EL-Ausdruck dynamisch entschieden.
<?xml version="1.0"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
<debug-output>true</debug-output>
<skin-family>
#{view.locale.language=='en' ? 'SkinA' : 'SkinB'}
</skin-family>
</trinidad-config>
Abbildung: Beispiel für einen Ausschnitt aus trinidad-config.xml
- debug-output
- client-validation
- client-validation-disabled
- output-mode
- skin-family
- accessibility-mode
- oracle-help-servlet-url
- page-flow-scope-lifetime
- time-zone
- two-digit-year-start
- right-to-left
- number-grouping-separator
- decimal-separator
- currency-code
- uploaded-file-processor
- formatting-locale
Hinweis:
Sämtliche Parameter dieser Konfigurationsdatei sind über die Klasse org.apache.myfaces.trinidad.context.RequestContext verfügbar.
debug-output
Dieser Konfigurationseintrag ist für die Entwicklungszeit gedacht und bewirkt, dass der Response formatiert wird, sodass dieser besser lesbar ist. Außerdem werden Kommentare bereitgestellt, die anzeigen, welche Komponente welchen HTML Block gerendert hat. Außerdem werden diverse evt. vorhandene Markup Probleme angezeigt.
client-validation
Mögliche Werte: INLINE, ALERT, DISABLED
Dieser Konfigurationseintrag legt das Verhalten von clientseitiger Konvertierung und Validierung fest. Durch INLINE werden Fehler in der Seite angezeigt, im Gegensatz hierzu wird mit ALERT ein JavaScript Alert Dialog angezeigt. Mit DISABLED wird die Anzeige eines Fehlers unterdrückt und Konvertierung und Validierung werden nur serverseitig durchgeführt.
client-validation-disabled
Dieser Konfigurationseintrag ist veraltet und wurde vor der Einführung vom Konfigurationseintrag " client-validation" verwendet.
output-mode
Mögliche Werte: default, printable und email
Mit diesem Konfigurationseintrag kann der Output für verschiedene Anwendungsfälle optimiert werden.
skin-family
Hier kann die Skin-Familie angegeben werden, welche für das Skinning verwendet werden soll.
accessibility-mode
Mögliche Werte: default, inaccessible, screenReader
Dieser Konfigurationseintrag steuert den Output in Hinblick auf Accessibility. Mit " default" werden Accessibility-Features unterstützt. Hingegen optimiert " inaccessible" den Output, indem keine Accessibility Angaben/Optimierungen in den Output aufgenommen werden. Mit " screenReader" werden spezielle Optimierungen für ScreenReader in den Output aufgenommen.
oracle-help-servlet-url
Über diesen Konfigurationseintrag kann eine URL angegeben werden, welche die Oracle Help for the Web (OHW) Funktionalität bereitstellt.
page-flow-scope-lifetime
Default: 15
Im Gegensatz zu anderen Konfigurationseinträgen ist dieser nicht EL-fähig. Mit diesem Eintrag kann angegeben werden, wie viele pageFlowScope Instanzen maximal gleichzeitig gehalten werden.
time-zone
Default: Wert des Browsers
Dieser Eintrag dient zum Setzen der Zeitzone, welche von org.apache.myfaces.trinidad.converter.DateTimeConverter verwendet werden soll.
two-digit-year-start
Default: 1950
Dieser Eintrag definiert den Jahresoffset, welche von org.apache.myfaces.trinidad.converter.DateTimeConverter verwendet werden soll, um Jahresangaben mit 2 Stellen zu parsen.
right-to-left
Default: false
Mit diesem Konfigurationseintrag kann die Leserichtung geändert werden. D.h. beim Rendering wird der Text entsprechend zur Leserichtung ausgegeben. Die Lesereichung rechts-nach-links ist für verschiedene Sprachen erforderlich. Sollen mehrere Sprachen unterstützt werden, so ist hier die Verwendung eines entsprechende EL-Ausdrucks sinnvoll.
number-grouping-separator
Default: Wird durch das aktuelle Lokale definiert
Dieser Eintrag gibt das Tausender-Trennzeichen bei Zahlen an, welches von org.apache.myfaces.trinidad.converter.NumberConverter verwendet werden soll.
decimal-separator
Default: Wird durch das aktuelle Lokale definiert
Dieser Eintrag gibt das Zeichen für den Dezimalpunk bei Zahlen an, welches von org.apache.myfaces.trinidad.converter.NumberConverter verwendet werden soll.
currency-code
Dieser Eintrag gibt den Währungscode nach ISO 4217 an, welcher zur Formatierung von Währungsangaben von org.apache.myfaces.trinidad.converter.NumberConverter verwendet wird.
uploaded-file-processor
Über diesen Konfigurationseintrag kann ein alternativer UploadProcessor angegeben werden. Dieser muss das Interface org.apache.myfaces.trinidad.webapp.UploadedFileProcessor implementieren. Die Standardimplementierung speichert einen Upload temporär, bevor eine weitere Verarbeitung erfolgen kann. Dies kann bspw. durch eine eigene Implementierung übersprungen werden.
formatting-locale
Hier kann ein Locale angegeben werden, welches für Converter verwendet wird, welche kein explizites Locale haben. Das Format entspricht dem Standardformat, welches bspw. aus der faces-config.xml bereits bekannt ist (also bspw. de_AT).
9.3 myGourmet meets Trinidad
In diesem Abschnitt wird die bisher entwickelte Beispielapplikation myGourmet auf Trinidad umgestellt. Die Basiskonfiguration inklusive eines rudimentären Templates ist im Step tr00 als Download verfügbar. Aufbauend auf dieser Basiskonfiguration wird in den nachfolgenden Abschnitten die Beispielapplikation schrittweise mit Trinidad und Facelets implementiert.9.3.1 Ausgangslage
Die Grundstruktur des Projekts, sowie grundlegende Konzepte (unabhängig von Trinidad) wurden bereits in den vorherigen Kapiteln ausführlich erläutert. In Step tr01 wird die erste Seite mit Funktionalität gefüllt. Als Startpunkt dient neben einer fertig konfigurierten Applikation die Trinidad Document-Komponente.tr:document
Der erste Unterschied zu Applikationen mit Standardkomponenten besteht in der Verwendung des tr:document -Tags. Dieser Tag muss die gesamte Seite umschließen und ist erforderlich, um Trinidad-Features zu nutzen. Aus diesem Grund wird dieser Tag in der Beispielapplikation zentral im Template verwendet.
Auch tr:document ist nicht an HTML gebunden. Daher sind HTML-spezifische Komponenten über den Namespace http://myfaces.apache.org/trinidad/html verfügbar. Aus diesem Namespace kann bspw. trh:head verwendet werden, um Informationen für den HTML-Head bereit zu stellen. Als Alternative zu trh:head gibt es ein Facet für tr:document , in welchem im Falle von HTML Inhalte für den HTML-Head angegeben werden können. Abbildung Beispiel für tr:document zeigt ein Beispiel, in welchem der Seitentitel angegeben wird.
<tr:document xmlns:tr="http://myfaces.apache.org/trinidad">
<f:facet name="metaContainer">
<title>myGourmet - JSF sample application</title>
</f:facet>
</tr:document>
Abbildung: Beispiel für tr:document
9.3.2 Start von MyGourmet mit Trinidad
In der bisherigen Implementierung der Beispielapplikation wurde die Registrierung eines Kunden vorgestellt und im Detail erklärt. Aufbauend auf diesen Erläuterungen wird die Beispielapplikation auf Trinidad umgestellt. Abgesehen von der Umstellung auf tr:document werden in diesem aufbauenden Beispiel einige Standardkomponenten ersetzt. So wird bspw. der Button für die Benutzerregistrierung in Abbildung Umstellung h:commandButton auf tr:commandButton umgestellt.<h:commandButton value="#{messages.register}" action="#{nav.register}"/>
wird zu
<tr:commandButton text="#{messages.register}" action="#{nav.register}"/>
Abbildung: Umstellung h:commandButton auf tr:commandButton
Durch das Hinzugefügt des Buttons wird die bereits bekannte Funktionalität der Seitennavigation implementiert. Hierfür werden die verfügbaren Standardkonzepte von JSF verwendet. Die folgende Seite fällt allerdings noch recht mager aus. Dieser Zwischenstand ist als Step tr01 downloadbar. Mehr Funktionalität wird im nächsten Abschnitt hinzugefügt. Außerdem wird das PPR Feature von Trinidad im Detail erklärt.
9.4 Ajax/Partial Page Rendering (PPR) in Action
Hinter PPR, also Partial Page Rendering, verbirgt sich das Konzept, welches auch hinter Ajax steht. Zusammenfassend geht es darum, dass eine Interaktion des Users / der Userin mit der Webapplikation nicht zwangsläufig zum kompletten Refresh der Seite führt. In aktuellen Trinidad Versionen ist PPR auf Basis von XMLHttpRequests implementiert. Dies ermöglicht, dass nur Teilbereiche der Seite aktualisiert werden. Serverseitig durchläuft der Request alle Phasen, welche JSF zur Verfügung stellt. Dementsprechend können Requests auch abgekürzt werden (bspw. durch den Aufruf von FacesContext.getCurrentInstance().renderResponse(); ).In diesem Abschnitt wird auf Step tr01 aufgebaut. Bisher unterscheidet sich das Ergebnis, welches mit Hilfe von Trinidad Komponenten erzielt wurde, nach außen noch nicht von der bisherigen Funktionalität. Dies wird sich in diesem Abschnitt ändern.
tr:form
Um PPR in der Anwendung einfacher zu gestalten unterscheidet sich tr:form von h:form , indem tr:form kein eigener Namingcontainer ist. Außerdem stellt tr:form zusätzliche Funktionalität für eine Standardschaltfläche (Attribut defaultCommand ) zur Verfügung. Weiters ermöglicht tr:form Dateiuploads. tr:form stellt auch selbst ein Attribut für PPR zur Verfügung.
<tr:document
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:tr="http://myfaces.apache.org/trinidad"
xmlns:c="http://java.sun.com/jstl/core">
<f:facet name="metaContainer">
<title>myGourmet - JSF sample application</title>
</f:facet>
...
<tr:form id="form" defaultCommand="defaultButton">
...
</tr:form>
...
</tr:document>
Abbildung: Ausschnitt des Templates - tr:form mit defaultCommand
Trinidad stellt bei Formularen wie bereits erwähnt eine Default-Button Funktionalität zur Verfügung, welche es ermöglicht, dass durch den Druck der Enter-Taste ein entsprechend registrierter Button ausgelöst wird. Hierbei muss die ID des Buttons mit dem Wert des defaultCommand-Attributes übereinstimmen. Diese Funktionalität kann natürlich auch unter Verwendung des PPR-Features weiter genutzt werden.
9.4.1 PPR mit Attributen
PPR kann durch Attribute der Trinidad Komponenten oder im Source Code angestoßen werden (siehe RequestContext ). Um das Formular für den ersten Registrierungsschritt (Seite crd_add_customer.xhtml) PPR-fähig zu machen, wird PPR mit Hilfe der entsprechenden Attribute realisiert.Die Eingabemöglichkeiten für Kreditkartendetails sollen mit Hilfe des rendered -Attributes optional eingeblendet werden. Hierfür wird zuerst h:selectBooleanCheckbox gegen tr:selectBooleanCheckbox ausgetauscht. Zusätzlich zum valueChangeListener - und dem immediate -Attribut wird ein Attribut names autoSubmit benötigt. Dies wird auf true gesetzt, damit der PPR-Request direkt nach dem Klick auf die Checkbox abgesetzt wird. Jetzt folgt das Ein-Ausblenden der Eingabemöglichkeiten für Kreditkartendetails.
Hinweis:
Bei Command Komponenten heißt dieses Attribut partialSubmit . In beiden Fällen wird die PPR Funktionalität von Trinidad angestoßen. Als Gegenstück zu diesen Attributen gibt es ein Attribut names partialTriggers . Hier können die ID's angegeben werden, welche zu Komponenten gehören, die einen PPR-Request anstoßen können. Falls hier ID's von mehreren Command-Komponenten angegeben werden sollen, können diese mit Leerzeichen getrennt hintereinander geschrieben werden.
Komponenten, die mit dem rendered -Attribut ausgeblendet sind, werden nicht in der Seite versteckt, sondern tatsächlich nicht gerendert. Daher können diese Komponenten nicht per partialTriggers -Attribut aktualisiert werden. Daher muss auch hier eine beliebige parent-Komponente auf den entsprechenden Trigger " registriert" werden. Im vorherigen Beispiel wird daher die ID " creditCardSelectionBox" in der parent-Komponente registriert (Attribute partialTriggers ). Damit nicht das gesamte Formular aktualisiert wird kann bspw. ein separates Panel um diesen Bereich gelegt werden. Dieses Panel wird immer gerendert, jedoch nur bei Bedarf mit den entsprechenden Kindkomponenten.
Wird ein PPR-Request angestoßen und gibt es in der Seite Komponenten, die auf die entsprechenden ID registriert sind, dann werden diese Komponenten inkl. ihrer Kindkomponenten aktualisiert. Im zuvor aufgeführten Beispiel wird für PPR das autoSubmit -Attribut verwendet. Dies bewirkt im vorliegenden Fall, dass der ValueChangeListener (serverseitig) ausgeführt wird, sobald eine entsprechende Interaktion des Users/der Userin mit der Komponente erfolgt. Im Vergleich hierzu wurde der ValueChangeListener ohne PPR bisher erst ausgeführt, nachdem das gesamte Formular abgeschickt wurde. (Achtung: es wird nicht bei jeder kleinen Änderung ein PPR-Request erzeugt. PPR wird normalerweise durch einen Klick oder durch das Drücken der Tab-Taste angestoßen, da somit die User-Interaktion für diese Inputkomponente für beendet gilt.)
Hinweis:
Muss auf Standardkomponenten zurückgegriffen werden, welchen die speziellen PPR Attribute nicht zur Verfügung stellen, dann können diese Komponenten in Trinidad Komponenten eingebettet werden. Diese umschließende Trinidad Komponente kann bei Bedarf angestoßen werden. Somit wird erreicht, dass auch die eingebetteten Standardkomponenten durch die PPR Funktionalität von Trinidad aktualisiert werden.
<tr:panelFormLayout partialTriggers="creditCardSelectionBox">
<tr:selectBooleanCheckbox
id="creditCardSelectionBox"
label="#{messages.use_credit_card}"
autoSubmit="true" immediate="true"
value="#{registrationPageBean.customer.useCreditCardPerDefault}"
valueChangeListener="#{registrationPageBean.useCreditCard}"/>
<tr:group rendered="#{registrationPageBean.customer.useCreditCardPerDefault}">
<tr:selectOneListbox
label="#{messages.credit_card_type}"
value="#{registrationPageBean.creditCardType}"
required="true">
<f:selectItems value="#{registrationPageBean.creditCardTypes}"/>
</tr:selectOneListbox>
<tr:inputText
label="#{messages.credit_card_number}"
value="#{registrationPageBean.customer.creditCardNumber}"
required="true"/>
</tr:group>
</tr:panelFormLayout>
public void useCreditCard(ValueChangeEvent event)
{
this.customer.setUseCreditCardPerDefault( (Boolean)event.getNewValue() );
FacesContext.getCurrentInstance().renderResponse();
}
Abbildung: Umstellung der optionalen Kreditkartendetails
Die Alternativ zur Verwendung des rendered -Attributes und der Aktualisierung der parent-Komponente ist die Verwendung der CSS Lösung ( inlineStyle=" display:none" )
Hinweis:
Je kleiner der aktualisierte Bereich in der Seite ist, desto unauffälliger verhält sich die Seite bei der Aktualisierung der Informationen.
Alle weiteren Umstellungen dieser ersten Seite auf Trinidad Komponenten haben keinen Bezug zum PPR-Feature von Trinidad. Die entsprechenden Eigenschaften der Komponenten werden im Kapitel " Advanced Trinidad Components" besprochen.
Hinweis:
In der Beispielapplikation wird tr:selectManyListbox verwendet. Allerdings gibt es in der aktuellen Trinidad Version (1.x.4) einen Bug, sodass der Inhalt nicht korrekt dargestellt wird (im Vergleich zu der Standardkomponente). Ein entsprechender Patch ist bereits verfügbar ( http://issues.apache.org/jira/browse/TRINIDAD-812 ). Alternativ kann die Standardkomponente verwendet werden, welche im Beispiel durch <ui:remove> " ausgeblendet " wird.
Hinweis:
Im bisher beschriebenen Beispiel kommt es zu einem Fehlverhalten, welches aufgrund eines Bugs verursacht wird. Ein entsprechender Patch wurde bereits erstellt und wird in Version 1.x.5 verfügbar sein ( http://issues.apache.org/jira/browse/TRINIDAD-842 ).
9.4.2 PPR per RequestContext im Source Code
In einem weiteren Schritt wird die Seite crd_add_customer_address.xhtml umgestellt. Hier wird PPR sowohl durch die entsprechenden Attribute, als auch durch den RequestContext verwendet. Zuerst wird die Funktionalität der Seite erweitert, indem angelegte Adressen direkt auf der Seite in einer Tabelle als Überblick dargestellt werden. Diese Tabelle soll sich inhaltlich anpassen, sobald Informationen hinzugefügt oder gelöscht werden. Allerdings soll nicht die gesamte Seite neu dargestellt werden, sondern mit Hilfe von PPR nur das Formular und die Tabelle selbst. Außerdem soll das Formular gelöscht werden, sobald eine Adresse hinzugefügt wurde.Daher stellen wir drei Schaltflächen zur Verfügung. Die erste Schaltfläche übernimmt die eingegebene Adresse und ermöglicht, dass eine weitere Adresse eingegeben werden kann. Ein zweiter Button ermöglicht es dem/der UserIn, dass die Adresse übernommen wird und automatisch die nächste Seite aufgerufen wird. Des weiteren soll ein dritter Button eingeblendet werden, sobald mindestens ein Eintrag in der Tabelle enthalten ist. Dieser Button soll ermöglichen, dass zum nächsten Schritt der Benutzerregistrierung gewechselt werden kann, ohne das Formular mit einer weiteren Adresse auszufüllen. Somit ergibt sich der Seiten-Ausschnitt aus Abbildung Schaltfälchen für die Verarbeitung von Adresseingaben .
<tr:commandButton id="add" text="#{messages.add}" partialSubmit="true" actionListener="#{registrationPageBean.addAddress}"/>
<tr:commandButton id="defaultButton" text="#{messages.add_and_next}" actionListener="#{registrationPageBean.addAddress}" action="#{nav.showCustomerSummary}"/>
<tr:commandButton text="#{messages.next}" immediate="true" action="#{nav.showCustomerSummary}" rendered="#{registrationPageBean.showAddressTable}"/>
Abbildung: Schaltfälchen für die Verarbeitung von Adresseingaben
Alle betroffenen Komponenten sind durch eine Trinidad Komponente (bspw. durch den Tag tr:panelGroupLayout ) gruppiert. Daher ist es durchaus sinnvoll das gesamte Panel zu aktualisieren, sobald der User/die Userin mit diesem Formular interagiert. Daher wird in diesem Tag das Attribut partialTriggers angegeben. Die ID, die in diesem Attribut hinterlegt wird, entspricht der ID eines Buttons. Dieser Schritt ist in Abbildung partialTriggers zur Aktualisierung des Formulars ausschnittsweise dargestellt.
<tr:panelGroupLayout partialTriggers="add">
...
<tr:panelFormLayout>...</tr:panelFormLayout>
<tr:commandButton id="add" text="#{messages.add}"
partialSubmit="true" actionListener="..."/>
...
</tr:panelGroupLayout>
Abbildung: partialTriggers zur Aktualisierung des Formulars
Das Eingabeformular selbst ist auch mit Trinidad Komponenten realisiert. Grundsätzlich sind diese sehr ähnlich zu den Standardkomponente. Eine genaue Betrachtung folgt im Kapitel " Advanced Trinidad Components" . Der ActionListener aus Abbildung ActionListener zum Hinzufügen der eingegebenen Adresse ist dafür verantwortlich, dass die eingegebene Adresse gespeichert wird und die Adresse für das Eingabeformular zurückgesetzt wird. Somit wird die eingegebene Adresse der Tabelle hinzugefügt wird und das Formular zurückgesetzt.
public void addAddress(ActionEvent event)
{
this.customer.getAddresses().add( this.address );
this.address = new Address();
}
Abbildung: ActionListener zum Hinzufügen der eingegebenen Adresse
Für das Löschen eines Tabelleneintrages und die Aktualisierung der Tabelle wird in der Beispielapplikation der RequestContext verwendet, um PPR per Source Code zu erläutern. Gibt es bspw. bereits mehrere Adresseinträge in der Tabelle und soll einer dieser Einträge wieder gelöscht werden, so kann dies bspw. über einen Link realisiert werden. Dieser Link wird mit einem ActionListener verbunden, welcher den Eintrag löscht und die Aktualisierung der Tabelle anstößt. Hierfür wird der Tabelle eine ID vergeben (in der Beispielapplikation " addressTable" ), welche im Source Code verwendet werden kann, um die entsprechende Komponente zu suchen. Diese Komponente wird dann den " partial targes" zugewiesen. Somit wird nur die Tabelle aktualisiert. D.h. wenn bereits begonnen wurde eine neue Adresse einzugeben und zwischendurch wird ein Tabelleneintrag gelöscht, wird nur die Tabelle aktualisiert und das (teilweise) ausgefüllte Formular bleibt erhalten. Somit kann selbst nach dieser Unterbrechung mit der Eingabe der Adresse fortgefahren werden. Abbildung Verwendung von RequestContext zur Verwendung von PPR im Source Code zeigt alle hierfür relevanten Ausschnitte der Tabelle.
<tr:table id="addressTable"
value="#{registrationPageBean.customer.addresses}"
var="entry"
...
immediate="true"
autoSubmit="true">
...
<tr:column>
<tr:commandLink text="#{messages.delete}" partialSubmit="true" actionListener="#{registrationPageBean.deleteAddress}" immediate="true"/>
</tr:column>
</tr:table>
public void deleteAddress(ActionEvent event)
{
CoreTable table = findTable( event.getComponent() );
customer.getAddresses().remove( table.getRowData() );
if(RequestContext.getCurrentInstance().isPartialRequest( FacesContext.getCurrentInstance() ))
{
triggerPartialPageRenderingForComponent( "addressTable" );
}
}
private void triggerPartialPageRenderingForComponent(String componentId)
{
FacesContext context = FacesContext.getCurrentInstance();
RequestContext.getCurrentInstance().addPartialTarget( context.getViewRoot().findComponent( componentId ) );
}
Abbildung: Verwendung von RequestContext zur Verwendung von PPR im Source Code
9.4.3 PPR über Attribute vs. PPR mit RequestContext (per Source Code)
Grundsätzlich ist die Verwendung der entsprechenden Attribute empfehlenswert. Ist die Aktualisierung von Seitenbereichen allerdings von Bedingungen im Source Code abhängig oder stößt die Lösung mit Hilfe der PPR-Attribute an Grenzen, so kann PPR per RequestContext im Source Code verwendet werden. Der Nachteil von PPR per Source Code ist, dass eine (wenn auch kurze) Implementierung erforderlich ist und PPR-Mechanismen somit nicht direkt in der Seite ersichtlich sind. Im Gegensatz hierzu wird PPR über Attribute automatisch von Trinidad analysiert und durchgeführt. Ein gutes Beispiel für " technische" Grenzen ist Issue http://issues.apache.org/jira/browse/TRINIDAD-842 . Als workaround kann hier bspw. PPR per RequestContext verwendet werden, um die Aktualisierung der gesamten Form anzustoßen. Somit bestehen bis zur Lösung bspw. eines Bugs geringfügige Nachteile (die Implementierung des workarounds und der Nachteil, dass ein größerer Bereich aktualisiert werden muss), allerdings ist es möglich die Funktionalität zur Verfügung zu stellen.9.4.4 PPR und JSF Namingcontainer
Soll PPR zwischen verschiedenen Namingcontainer verwendet werden, so muss der Namingcontainer auch im partialTriggers -Attribut angegeben werden. Dies sieht bspw. wie folgt aus:partialTriggers=" theSubform:theLink"
Für Componenten im root der Seite wird nur ein Doppelpunkt angegeben:
partialTriggers=" :someRootComponent"
Gibt es mehrere Namingcontainer und soll auf den darüber liegenden verwiesen werden, dann müssen 2 Doppelpunke angegeben werden. Für jeden weiteren darüber liegenden Namingcontainer muss ein weiterer Doppelpunk hinzugefügt werden.
9.4.5 PPR Funktionalität ohne zusätzlicher Konfiguration
Die folgenden Trinidad-Elemente unterstützen PPR ohne zusätzliche Konfigurationen für Quelle und Ziel:- table
- treeTable
- selectRangeChoicebar
- showDetail
- showDetailHeader
- panelTabbed
- panelAccordion
- poll
- chooseDate
- Dialog Framework (return events)
9.4.6 Weitere PPR Hints
Natürlich ist es möglich Command-Komponenten mit dem Attribut partialSubmit zu versehen, ohne die ID dieser Komponente als partialTrigger zu registrieren. Dies bewirkt den Aufruf der entsprechenden Serverlogik, allerdings bleibt die Seite unverändert (sofern serverseitig nicht PPR per RequestContext zum Einsatz kommt). Daher muss man diese Möglichkeit mit bedacht einsetzten, da der User/die Userin in diesem Fall keine Rückmeldung von der Seite bekommt, ob die entsprechende Aktion tatsächlich durchgeführt wurde.Eine weitere Möglichkeit, welche bisher nicht erläutert wurde ist, dass Command-Komponenten ebenfalls das partialTriggers -Attribut besitzen. Somit können diese von anderen Komponenten (oder sich selbst) angestoßen werden, damit eine Aktualisierung der Command-Komponente selbst erfolgt. Beispielsweise wäre denkbar über diesen Mechanismus Command-Komponenten zu de-/aktivieren.
Bisher wurden hauptsächlich ActionEvents betrachtet. Allerdings stößt jedes serverseitiges Event ( ValueChangeEvent , SortEvent , ...) eine Aktualisierung der Entsprechenden Komponenten an, sofern die entsprechende Trigger registriert wurden.
Das Aktualisieren einzelner Komponenten über den RequestContext ist nur in einem PPR-Request sinnvoll, da außerhalb eines solchen Requests sowieso die gesamte Seite aktualisiert wird. Es ist jedoch nicht erforderlich im Source Code Unterscheidungen zu treffen. Wird die Funktion addPartialTarget außerhalb eines PPR-Requests ausgeführt, so hat dies keine weiteren Konsequenzen. Muss aus anderen Gründen festgestellt werden, ob es sich um einen PPR-Request handelt oder nicht, dann kann dies über den Funktionsaufruf isPartialRequest aus Abbildung Verwendung von RequestContext zur Verwendung von PPR im Source Code herausgefunden werden. Im vorliegenden Fall wird der deleteAddress-ActionListener vor der Bearbeitung einer Adresse wiederverwendet. Wie bereits erwähnt ist die Unterscheidung mit Hilfe dieses Methodenaufrufs nicht erforderlich. Im Falle einer Adressbearbeitung wird die Adresse durch einen speziellen ActionListener in den Request Scope kopiert und durch den deleteAddress-ActionListener aus der Adressen-Liste entfernt. Abschließend wird eine neue Seite angezeigt. Somit stößt der Link zum Löschen eines Eintrags den gleichen ActionListener an, wie der Link für die Bearbeitung eines Eintrags. Aufgrund des Seitenwechsels bei der Bearbeitung eines Eintrags bedarf es grundsätzlich keiner Unterscheidung zwischen den beiden Request-Arten. Würde der PPR-Request allerdings eine fachlich aufwändigere Verarbeitung bewirken, so könnte mit dieser Unterscheidungsmöglichkeit diese Verarbeitung nur auf den PPR-Request beschränkt werden.
9.5 Trinidad Dialog Framework in Action
In Applikationen ist eine häufige Anforderung gewisse Schritte in einem Wizard zur Verfügung zu stellen. Beispielsweise soll die ursprüngliche Seite geladen bleiben und für die Eingabe zusätzlicher Informationen ein neues Fenster angezeigt werden. Nachdem diese Eingaben abgeschlossen sind, wird der Wizard (auch Dialog genannt) geschlossen und die ursprüngliche Seite ist wieder im Vordergrund. Außerdem soll diese Seite mit den Ergebnissen des Dialogs weiter arbeiten können.In der bisherigen Implementierung von myGourmet gibt es bereits ein Dialogszenario. Die Benutzerregistrierung erfolgt in aufeinander folgenden Schritten und kann somit als Dialog bezeichnet werden.
Bei Trinidad gibt es folgende Möglichkeiten Dialoge zur Verfügung zu stellen:
- gleiches Fenster
- neues Fenster
- leichtgewichtig (Dialog wird in der Seite direkt angezeigt)
9.5.1 Dialoge im gleichen Fenster
Soll ein Dialog für den User als solches nicht von anderen Seiten zu unterscheiden sein, oder unterstützt ein Device keine neuen Fenster, so kann/wird der Dialog im gleichen Fenster dargestellt. Abbildung Beispiel für eine Navigationsregel und eine Action-Methode, um einen Dialog zu starten zeigt, dass einerseits der String der Action-Methode geändert werden muss und andererseits ist die entsprechende Anpassung der Navigation erforderlich. public String register()
{
return "dialog:register";
}
<navigation-rule>
<from-view-id>/pages/home.xhtml</from-view-id>
<navigation-case>
<from-outcome>dialog:register</from-outcome>
<to-view-id>/pages/add_customer.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Abbildung: Beispiel für eine Navigationsregel und eine Action-Methode, um einen Dialog zu starten
Hinweis:
Da der Dialog jetzt nicht mehr das Template der gesamten Seite benötigt wird für Dialoge ein eigenes Template erstellt und eingebunden.
9.5.2 Dialoge als Popup
Zum Starten eines Popup-Dialoges sind ebenfalls die Schritte aus Abbildung Beispiel für eine Navigationsregel und eine Action-Methode, um einen Dialog zu starten erfolgreich. Zusätzlich muss das useWindow -Attribut der Command-Komponente auf true gesetzt werden. In der Beispielapplikation ist dies die Komponente tr:commandButton in der Seite home.xhtml . Damit die Seite, von welcher der Dialog gestartet wird, nicht neu geladen werden muss, ist es sinnvoll das Attribut partialSubmit ebenfalls auf true zu setzen. Abbildung Button, um einen Dialog zu starten zeigt wie sich diese Änderung in der Beispielapplikation präsentiert.<tr:commandButton id="defaultButton" text="#{messages.register}" action="#{nav.register}" useWindow="true" partialSubmit="true"/>
Abbildung: Button, um einen Dialog zu starten
<tr:commandButton id="defaultButton" text="#{messages.register}" action="#{nav.register}" immediate="true" useWindow="true" partialSubmit="true" windowWidth="800" windowHeight="600"/>
Abbildung: Button, um einen Dialog in einer bestimmten Fenstergröße zu starten
9.5.3 Leichtgewichtige Dialoge
Leichtgewichte Dialog unterscheiden sich vom Vorgehen (bezüglich der Verwendung) beinahe nicht von Popup-Dialogen. Der einzige Unterschied ist das Kontextparameter org.apache.myfaces.trinidad.ENABLE_LIGHTWEIGHT_DIALOGS in der web.xml . Wird dieses auf true gesetzt, so werden keine Popups erzeugt, sondern es wird ein div über die Seite gelegt, welches ein iframe enthält. Dieses iframe ist für die Darstellung der Dialogseiten verantwortlich. Die Seite im Hintergrund ist außerdem disabled und kann nicht angesprochen werden, bis der Dialog wieder geschlossen wird.Hinweis:
Im Vergleich zu Popup-Dialogen funktionieren die Attribute windowWidth und windowHeight bis inkl. Version 1.x.4 nur, wenn beide Attribute angegeben sind. Dieses Problem ist ab Version 1.x.5 behoben (siehe https://issues.apache.org/jira/browse/TRINIDAD-841).
9.5.4 Dialog - das Finish
Ein Dialog stellt eine endliche Menge an zusammenhängenden Seitenabfolgen dar. Das heißt im Falle von myGourmet, dass alle Seiten, welche zur Registrierung eines Accounts erforderlich sind einen Dialog bilden. Ein Dialog kann ordnungsgemäß oder vorzeitig beendet werden. Beide Fälle werden in myGroumet demonstriert. Solang ein(e) BenutzerIn die Registrierung nicht abgeschlossen hat, kann der Dialog über einen Button zum Abbrechen vorzeitig beendet werden. In diesem Fall wird das Customer -Objekt in der Session resettet, ein entsprechendes Ergebnis an die ursprüngliche Seite zurückgeliefert und der Dialog geschlossen. Dies ist in Abbildung Dialoge abbrechen ersichtlich. public void cancelRegistration(ActionEvent event)
{
setupNewCustomer();
RequestContext.getCurrentInstance().returnFromDialog( false, null );
}
private void setupNewCustomer()
{
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().createValueBinding( "#{customer}" ).setValue( context, new Customer());
}
Abbildung: Dialoge abbrechen
Auf der Seite home.xhtml wird abhängig vom Ergebnis ein passender Text angezeigt. Dies wird ebenfalls über PPR realisiert, da auch diese Dialog-Events einen PPR Request anstoßen können. Entsprechend wird die ID des Buttons, welcher den Dialog startet, der Textkomponente im Attribut partialTriggers hinzugefügt. Abbildung Werte an Dialoge übergeben und von Dialogen zurückgeben zeigt die entsprechende Textkomponente.
Die Attribute des tr:commandButton für die Dialog-Listener heißen launchListener und returnListener und sind ebenfalls in Abbildung Werte an Dialoge übergeben und von Dialogen zurückgeben dargestellt.
<tr:outputText
value="#{registrationPageBean.customer.loggedIn == true ? messages.registration_finished : messages.not_registered}"
partialTriggers="defaultButton"/>
<tr:commandButton id="defaultButton"
text="#{messages.register}"
action="#{nav.register}"
immediate="true"
useWindow="true"
partialSubmit="true"
windowWidth="700"
windowHeight="500"
launchListener="#{registrationPageBean.launchRegistrationDialog}"
returnListener="#{registrationPageBean.returnRegistrationDialog}"/>
public void launchRegistrationDialog(LaunchEvent event)
{
setupNewCustomer();
}
public void returnRegistrationDialog(ReturnEvent event)
{
Object returnValue = event.getReturnValue();
if( returnValue == null || (Boolean)returnValue == false)
{
setupNewCustomer();
}
else
{
this.customer.setLoggedIn( true );
}
}
Abbildung: Werte an Dialoge übergeben und von Dialogen zurückgeben
<tr:commandButton id="defaultButton" text="#{messages.finish}" actionListener="#{registrationPageBean.finishRegistration}"/>
public void finishRegistration(ActionEvent event)
{
RequestContext.getCurrentInstance().returnFromDialog( true, null );
}
Abbildung: Ergebnisse eines Dialogs
Hinweis:
Außerdem wurde in der Klasse RegistrationPageBean das address -Property umgestellt. In step tr03 wird hier nicht mehr direkt eine Instanz erzeugt, sondern es wird eine Instanz über die managed Properties Funktionalität injected. Die Funktionalität ändert sich hingegen nicht.
9.6 PageFlowScope in Dialogabläufen
Welcher Scope für Beans gewählt werden soll, ist eine häufig gestellte Frage. Gerade bei Dialogabläufen ist der Request Scope zu kurz und der Session Scope zu lange. Mittlerweile gibt es viele Möglichkeiten Scopes einzusetzen, welche länger als der Request Scope sind, jedoch kürzer als der Session Scope. Trinidad stellt hierfür ebenfalls einen Scope namens PageFlowScope zur Verfügung. Dieser Scope kann entweder im Source Code manuell verwaltet werden, oder es wird ein spezieller ActionListener eingebunden.In step tr03 wurde bereits eine Möglichkeit implementiert, wie der Request Scope verlängert werden kann. Für die Bearbeitung von Adressen wird in tr03 das Tag tr:setActionListener verwendet. Mit diesem speziellen ActionListener kann ein Wert kopiert werden. Hierbei gibt es zwei Möglichkeiten. Im Beispiel wird der Wert genau um einen Request verlängert, da der Wert in ein Bean im Request Scope kopiert wird. Die Implementierung in myGourmet ist in Abbildung Verwendung von tr:setActionListener ersichtlich.
<tr:column>
<tr:commandLink text="#{messages.edit}" action="#{nav.editAddress}" immediate="true"
actionListener="#{registrationPageBean.deleteAddress}">
<tr:setActionListener from="#{entry}" to="#{registrationPageBean.address}"/>
</tr:commandLink>
</tr:column>
Abbildung: Verwendung von tr:setActionListener
RequestContext.getCurrentInstance().getPageFlowScope()
Der Nachteil dieser Scope Implementierung ist, dass die Objekte so lange im Scope existieren, bis dieser manuell beendet wird. Folgender Ausschnitt zeigt den erfolgreichen Aufruf.
RequestContext.getCurrentInstance().getPageFlowScope().clear();
Wird dies nicht gemacht, so gibt es keinen Unterschied zum Session Scope. Weitere Nachteile sind:
- In EL Ausdrücken muss der pageFlowScope -Prefix verwendet werden. Hierfür gibt es derzeit keine IDE Unterstützung.
- Soll sich der Scope über mehrere Seiten erstrecken, so ist eine zusätzliche Implementierung nötig. Der Scope kann nicht in der faces-config.xml konfiguriert werden.
- Der Scope spielt nicht ganz sauber mit dem PageBean-Konzept zusammen, bei welchem in der Seite ein PageBean im Request Scope verwendet wird, welchem Objekte über die Konfiguration aus einem längeren Scope injected werden.
Im Abschnitt ATC wird ein Bestelldialog implementiert, welcher die bisher beschriebenen Mechanismen verwendet. Zur besseren Übersicht bekommen alle Seiten, welche zu einem Dialog gehören einen eigenen Prefix im Seitennamen. Die Seiten für den Registrierungsdialog bekommen den Prefix " crd" (customer registration dialog) und die Seiten für des Bestelldialogs den Prefix " pod " (product order dialog).
9.7 Clientseitige Konvertierung und Validierung
Standardmäßig konvertiert und validiert JSF serverseitig. Das heißt, dass bei jeder falschen Eingabe ein Request zum Server verursacht wird. Schlägt die Konvertierung oder Validierung fehl, so wird die Seite wieder an den Client gerendert und sofern dies festgelegt wurde, wird eine Fehlermeldung angezeigt. Trinidad ermöglicht es mit clientseitiger Konvertierung und Validierung, dass dieser Server-Request entfällt, wenn Konvertierung oder Validierung fehl schlägt. Hierzu können entweder Trinidad Komponenten mit clientseitigen Mechanismen verwendet werden oder es muss entsprechende Logik in JavaScript implementiert werden.Eine häufige Anforderung ist die Validierung von E-Mail Adressen. In der ersten Dialogseite der Kundenregistrierung ist die Eingabe der E-Mail Adresse erforderlich. Bisher konnte jeder beliebige Wert eingegeben werden. Die Validierung einer E-Mail Adresse kann bspw. mit Hilfe einer Regular-Expression umgesetzt werden. Hierzu bietet Trinidad eine passende Komponente, welche zusätzlich die Validierung clientseitig übernimmt. Abbildung Beispiel für Komponenten mit clientside validation zeigt die Änderungen in der Seite.
<tr:inputText label="#{messages.email}" value="#{registrationPageBean.customer.email}" required="true">
<tr:validateRegExp pattern="#{registrationPageBean.emailRegExp}" messageDetailNoMatch="#{messages.no_valid_email}"/>
</tr:inputText>
Abbildung: Beispiel für Komponenten mit clientside validation
Außerdem wird bei einem Validierungsfehler neben dem Label des Eingabefelds standardmäßig ein rotes X platziert. Zusätzlich wird unterhalb des Eingabefelds ein entsprechender Fehlertext angezeigt.
Zusätzlich zu den out-of-the-box Mechanismen für clientseitige Konvertierung und Validierung ist es möglich eigene JavaScript Implementierungen zu nutzen. In der Beispielapplikation wird in step tr04 ein eigener Validator für die Kategorien implementiert. Inhalt der gezeigten Implementierung ist, dass der Eintrag " keine" nur alleine, also in keiner Kombination mit anderen Einträgen, vorkommen darf.
Hinweis:
Die clientseitige Validierung von Multi-Select Komponenten ist ab Trinidad 1.x.5 möglich. Der entsprechende Patch steht unter https://issues.apache.org/jira/browse/TRINIDAD-860 zur Verfügung.
In bisherigen Kapiteln wurden die serverseitige Validierung demonstriert. Abbildung Beispiel für einen konventionellen Validator zeigt eine mögliche Implementierung der zuvor beschriebenen Anforderungen.
public class CategoryValidator implements Validator
{
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
if(value == null || !( value instanceof List ))
{
return;
}
List categories = (List)value;
if(categories.size() > 1 && categories.contains( "-1" ))
{
ResourceBundle messages = ResourceBundle.getBundle( "at.irian.jsfbook.messages",
FacesContext.getCurrentInstance().getViewRoot().getLocale() );
throw new ValidatorException( new FacesMessage( messages.getString( "category_validator_details" ) ) );
}
}
}
Abbildung: Beispiel für einen konventionellen Validator
public class CategoryValidator implements Validator, ClientValidator
{
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException
{
if (value == null || !( value instanceof List ))
{
return;
}
List categories = (List) value;
if (categories.size() > 1 && categories.contains( "-1" ))
{
ResourceBundle messages = ResourceBundle.getBundle( "at.irian.jsfbook.messages",
FacesContext.getCurrentInstance().getViewRoot().getLocale() );
throw new ValidatorException( new FacesMessage( messages.getString( "category_validator_details" ) ) );
}
}
public String getClientLibrarySource(FacesContext context)
{
return FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath() + "/jsLibs/categoryValidator.js";
}
public Collection<String> getClientImportNames()
{
return null;
}
public String getClientScript(FacesContext context, UIComponent component)
{
return null;
}
public String getClientValidation(FacesContext context, UIComponent component)
{
ResourceBundle messages = ResourceBundle.getBundle( "at.irian.jsfbook.messages", FacesContext.getCurrentInstance().getViewRoot().getLocale() );
return "new TrCategoryValidator({" + "summary:'" + messages.getString( "category_validator_summary" ) + "'," + "details:'" + messages.getString( "category_validator_details" ) + "'})";
}
}
Abbildung: Beispiel für einen clientside Validator - die Java Seite
Die Entsprechende JavaScript Implementierung ist in Abbildung Beispiel für einen clientside Validator - die JavaScript Seite dargestellt.
function TrCategoryValidator(messages)
{
this.messages = messages;
}
function validateCategory(value, label, converter)
{
if(value.length > 1)
{
for(var i = 0; i < value.length; i++)
{
if(value[i] == "-1")
{
var facesMessage = new TrFacesMessage(
this.messages[TrCategoryValidator.SUMMARY],
this.messages[TrCategoryValidator.DETAILS],
TrFacesMessage.SEVERITY_ERROR);
throw new TrValidatorException(facesMessage);
}
}
}
}
TrCategoryValidator.prototype = new TrValidator();
TrCategoryValidator.prototype.validate = validateCategory;
TrCategoryValidator.SUMMARY = 'summary';
TrCategoryValidator.DETAILS = 'details';
Abbildung: Beispiel für einen clientside Validator - die JavaScript Seite