jakarta.faces.impl
angegeben:dataTable
dar, mit Bearbeiten-Link in jeder Zeile und
einem Neu-Button. Beim OK-Klick springt man zur Kuchenliste zurück.dataTable
dar, mit Bearbeiten-Link in jeder Zeile und
einem Neu-Button. KuchenListeHandler
(Request Scope): Wird in "kuchenliste.xhtml" verwendet und kann die Liste aller Kuchen liefern. KuchenDetailHandler
(Request Scope): Wird in "kuchendetail.xhtml" verwendet und übernimmt alle Aktionen die mit einem Kuchen zusammenhängen. ZutatDetailHandler
(Request Scope): Wird in "zutatdetail.xhtml" verwendet und übernimmt alle Aktionen die mit einer Zutat zusammenhängen. kuchenDetailHandler.neuKuchen
. Rückgabe dieser Methode (Navigationsziel): kuchendetail
.
kuchenDetailHandler.editKuchen
. Rückgabe dieser Methode (Navigationsziel): kuchendetail
.
Benötigte Parameter: ID des gewählten Kuchens. kuchenDetailHandler.saveKuchen
. Rückgabe dieser Methode (Navigationsziel): kuchenliste
.
Benötigte Parameter: ID des aktuellen Kuchens. zutatDetailHandler.editZutat
. Rückgabe dieser Methode (Navigationsziel): zutatdetail
.
Benötigte Parameter: ID des aktuellen Kuchens und ID der gewählten Zutat. zutatDetailHandler.neuZutat
. Rückgabe dieser Methode (Navigationsziel): zutatdetail
.
zutatDetailHandler.saveZutat
. Rückgabe dieser Methode (Navigationsziel): kuchendetail
.
Benötigte Parameter: ID des aktuellen Kuchens und ID der aktuellen Zutat. kuchenliste.xhtml
wird auf den Submit-Button "kuchenneu" des Formulars "formkuchenneu" geklickt.JSF-Framework
ruft die zugehörige Action KuchenDetailHandler.neuKuchen()
auf.KuchenDetailHandler.neuKuchen()
setzt die interne Kuchen-ID über KuchenDetailHandler.setKuchenId()
auf "0".KuchenDetailHandler.setKuchenId()
weist die Kuchen-ID "0" der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.KuchenDetailHandler.initKuchen()
erkennt anhand der ID "0", dass ein neuer Kuchen angelegt werden soll, und initialisiert
die Membervariable kuchenAktuell
mit einer neuen Instanz der KuchenBean
KuchenDetailHandler.neuKuchen()
gibt das Navigation Result "kuchendetail" zurück.JSF-Framework
rendert die Seite "kuchendetail.xhtml"kuchendetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei KuchenDetailHandler.getKuchenId()
auf.
Es kommt "0" zurück.kuchendetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei KuchenDetailHandler.getName()
auf.
Es kommt ein Leerstring zurück.kuchendetail.xhtml
den Submit-Button "kuchensave" des Formulars "formkuchen".KuchenDetailHandler.beforePhase()
wird die Kuchen-ID "0" aus dem Hidden Field "kuchenId" gezogen.KuchenDetailHandler.beforePhase()
setzt die interne Kuchen-ID über KuchenDetailHandler.setKuchenId()
auf "0".KuchenDetailHandler.setKuchenId()
weist die Kuchen-ID "0" der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.KuchenDetailHandler.initKuchen()
erkennt anhand der ID "0", dass ein neuer Kuchen angelegt werden soll, und initialisiert
die Membervariable kuchenAktuell
mit einer neuen Instanz der KuchenBean
JSF-Framework
führt die Validierung für das Feld "name" durch (Eingabe soll mindestens 5 Zeichen lang sein). Im Fehlerfall würde hier erneut das Eingabeformular gerendert.JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über KuchenDetailHandler.setKuchenId()
wird die
Kuchen-ID "0" gesetzt.KuchenDetailHandler.setKuchenId()
weist die Kuchen-ID "0" der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.KuchenDetailHandler.initKuchen()
erkennt anhand der ID "0", dass ein neuer Kuchen angelegt werden soll, und initialisiert
JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über KuchenDetailHandler.setName()
wird der
eingegebene Kuchen-Name in die Membervariable kuchenAktuell
gesetzt.JSF-Framework
ruft die Action KuchenDetailHandler.kuchenSave
auf.KuchenDetailHandler.kuchenSave
ruft KuchenZutatWorkerBean.saveKuchen
auf und speichert den neuen Kuchen. Die ID wird in der Membervariablen
intKuchenId
gespeichert.KuchenDetailHandler.kuchenSave
gibt "kuchenliste" zurück.JSF-Framework
rendert gemäß dieser Rückgabe die Seite "kuchenliste.xhtml".kuchenAktuell
-Membervariablen ist unterschiedlich.kuchenliste.xhtml
wird auf den Submit-Button "kuchenedit" des Formulars "formkuchenedit" geklickt.JSF-Framework
ruft den ActionListener KuchenDetailHandler.selectKuchen(ActionEvent)
auf. Dort wird eine Komponente UIParameter
gesucht, und aus ihrem Value
wird die ID des zu bearbeitenden Kuchens geholt und in die Membervariable kuchenId
geschrieben. KuchenDetailHandler.selectKuchen(ActionEvent)
ruft die interne Methode setKuchenId(...)
auf.KuchenDetailHandler.setKuchenId(...)
weist die übergebene ID der Membervariablen intKuchenId
zu
und ruft initKuchen
auf.KuchenDetailHandler.initKuchen()
erkennt, dass ein vorhandener Kuchen (ID > 0) in dieser Klasse vorliegt, und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
.JSF-Framework
ruft die zugehörige Action KuchenDetailHandler.editKuchen()
auf. Diese gibt nur das Navigation Result "kuchendetail" zurück.JSF-Framework
rendert die Seite "kuchendetail.xhtml"kuchendetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei KuchenDetailHandler.getKuchenId()
auf. Es kommt die Id des zu bearbeitenden Kuchens zurück, diese
wird in ein Hidden Field geschrieben.kuchendetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei KuchenDetailHandler.getName()
auf. Es kommt der Name des zu bearbeitenden Kuchens zurück.kuchendetail.xhtml
den Submit-Button "kuchensave" des Formulars "formkuchen".KuchenDetailHandler.beforePhase()
wird die echte Kuchen-ID (> 0) aus dem Hidden Field "kuchenId" gezogen.KuchenDetailHandler.beforePhase()
setzt die interne Kuchen-ID über KuchenDetailHandler.setKuchenId()
auf "0".KuchenDetailHandler.setKuchenId()
weist die echte Kuchen-ID (> 0) der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.KuchenDetailHandler.initKuchen()
erkennt anhand der echten ID (> 0), dass ein vorhandener Kuchen bearbeitet wird, und
und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
JSF-Framework
führt die Validierung für das Feld "name" durch (es muss ein Wert vorhanden sein und die Eingabe soll mindestens 5 Zeichen lang sein). Im Fehlerfall würde hier erneut das Eingabeformular gerendert.JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über KuchenDetailHandler.setKuchenId()
wird die
Kuchen-ID aus dem Hidden Field gesetzt.KuchenDetailHandler.setKuchenId()
weist die echte Kuchen-ID (> 0) der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.
Dieser und der folgende Schritt sind bereits in 2.1 geschehen, also erfolgt in diesem Use-Case ein doppeltes Laden des Kuchens.KuchenDetailHandler.initKuchen()
erkennt anhand der echten ID (> 0), dass ein vorhandener Kuchen bearbeitet wird, und
und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über KuchenDetailHandler.setName()
wird der
eingegebene Kuchen-Name in die Membervariable kuchenAktuell
gesetzt.JSF-Framework
ruft die Action KuchenDetailHandler.kuchenSave
auf.KuchenDetailHandler.kuchenSave
ruft KuchenZutatWorkerBean.saveKuchen
auf und speichert den neuen Kuchen. Die ID wird in der Membervariablen
intKuchenId
gespeichert.KuchenDetailHandler.kuchenSave
gibt "kuchenliste" zurück.JSF-Framework
rendert gemäß dieser Rückgabe die Seite "kuchenliste.xhtml".kuchendetail.xhtml
baut die Zutatenliste auf: sie werden über KuchenDetailHandler.getZutaten
abgerufen.KuchenDetailHandler.getZutaten
gibt die Zutatenliste aus kuchenAktuell.getZutaten
zurück.kuchendetail.xhtml
ID, Name und Menge der Zutat aus (ZutatBean.getId
, ZutatBean.getZutatName
, ZutatBean.getMenge
).kuchendetail.xhtml
wird auf den Submit-Button "zutatedit" des Formulars "formzutatedit" geklickt.JSF-Framework
führt das Phase-Event KuchenDetailHandler.beforePhase(PhaseEvent)
auf. Der Grund dafür wird im Abschnitt
ViewState/LifeCycle beschrieben. Die Kuchen-ID wird aus einem Request-Parameter "kuchenId" geholt und an setKuchenId
übergeben.KuchenDetailHandler.setKuchenId()
weist die echte Kuchen-ID (> 0) der Variablen "intKuchenId" zu und ruft KuchenDetailHandler.initKuchen()
auf.KuchenDetailHandler.initKuchen()
erkennt anhand der echten ID (> 0), dass ein vorhandener Kuchen bearbeitet wird, und
und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
JSF-Framework
ruft den ActionListener ZutatDetailHandler.selectKuchenZutat(ActionEvent)
auf. Dort wird eine Komponente UIParameter
namens "zutatId"
gesucht, und aus ihrem Value
wird die ID der zu bearbeitenden Zutat geholt und in die Membervariable zutatId
geschrieben.ZutatDetailHandler.selectKuchenZutat(ActionEvent)
wird außerdem die ActionListener-Methode ZutatDetailHandler.selectKuchen(ActionEvent)
aufgerufen .
Dort wird eine Komponente HtmlInputHidden
gesucht, und aus ihrem Value
wird die ID des zu bearbeitenden Kuchens geholt und in die Membervariable kuchenId
geschrieben.ZutatDetailHandler.selectKuchenZutat(ActionEvent)
wird ZutatDetailHandler.initZutat()
aufgerufen.ZutatDetailHandler.initZutat)
wird die aktuelle ZutatBean
über KuchenZutatWorkerBean.findZutatById(this.intZutatId)
geladen.ZutatDetailHandler.editZutat
wird aufgerufen. Es wird "zutatdetail" zurückgegeben.JSF-Framework
rendert gemäß dieser Rückgabe die Seite "zutatdetail.xhtml".zutatdetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei ZutatDetailHandler.getKuchenId()
und ZutatDetailHandler.getZutatId()
auf.
Die Ids werden in ein Hidden Field geschrieben.kuchendetail.xhtml
baut das Bearbeiten-Formular auf und ruft dabei ZutatDetailHandler.getZutatName()
und ZutatDetailHandler.getMenge()
auf.zutatdetail.xhtml
den Submit-Button "zutatsave" des Formulars "formzutatedit".ZutatDetailHandler.beforePhase()
wird die echte Kuchen-ID (> 0) aus dem Hidden Field "kuchenId" gezogen.ZutatDetailHandler.beforePhase()
wird die echte Zutat-ID (> 0) aus dem Hidden Field "zutatId" gezogen.ZutatDetailHandler.beforePhase()
setzt die interne Zutat-ID über ZutatDetailHandler.setZutatId()
.ZutatDetailHandler.setZutatId()
weist die echte Zutat-ID (> 0) der Variablen "intZutatId" zu und ruft ZutatDetailHandler.initZutat()
auf.ZutatDetailHandler.initZutat()
erkennt anhand der echten ID (> 0), dass eine vorhandene Zutat bearbeitet wird, und
und lädt ein Objekt ZutatBean
über KuchenZutatWorkerBean.findZutatById
JSF-Framework
führt die Validierung für das Feld "zutatName" und "menge" durch (Werte müssen vorhanden sein). Im Fehlerfall würde hier erneut das Eingabeformular gerendert.JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über ZutatDetailHandler.setKuchenId()
wird die Kuchen-ID aus dem Hidden Field gesetzt.JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über
ZutatDetailHandler.setZutatId()
wird die Zutat-ID aus dem Hidden Field gesetzt.ZutatDetailHandler.setZutatId()
weist die echte Zutat-ID (> 0) der Variablen "intZutatId" zu und ruft ZutatDetailHandler.initZutat()
auf.ZutatDetailHandler.initZutat()
erkennt anhand der echten ID (> 0), dass eine vorhandene Zutat bearbeitet wird, und
und lädt ein Objekt ZutatBean
über KuchenZutatWorkerBean.findZutatById
(auch hier doppeltes Laden der Zutat).JSF-Framework
wertet den Request aus und führt das Value-Binding des Formulars durch: über ZutatDetailHandler.setZutatName()
und
ZutatDetailHandler.setMenge()
werden die eingegebenen Daten in die Membervariable zutatAktuell
über ZutatBean.setZutatName()
und ZutatBean.setMenge()
gesetzt.JSF-Framework
ruft den ActionListener KuchenDetailHandler.selectKuchen
auf (Grund: nach dem Speichern der Zutat soll die KuchenDetail-Seite
angezeigt werden, und diese muss mit der Kuchen-Id initialisiert sein!).KuchenDetailHandler.selectKuchen(ActionEvent)
ruft die interne Methode setKuchenId()
auf.KuchenDetailHandler.setKuchenId(...)
weist die übergebene ID der Membervariablen intKuchenId
zu
und ruft initKuchen
auf.KuchenDetailHandler.initKuchen()
erkennt, dass ein vorhandener Kuchen (ID > 0) in dieser Klasse vorliegt, und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
.JSF-Framework
ruft die Action ZutatDetailHandler.zutatSave
auf.ZutatDetailHandler.zutatSave
ruft KuchenZutatWorkerBean.saveZutat
auf und speichert die neue Zutat. Die ID wird in der Membervariablen
intZutatId
gespeichert.ZutatDetailHandler.zutatSave
setzt den in Schritt 3.5 im KuchenDetailHandler
initialisierten Kuchen übre
KuchenDetailHandler.setKuchenId
(da der Schritt 3.5 geladene Kuchen natürlich die geänderte Zutat nicht enthält)KuchenDetailHandler.setKuchenId(...)
weist die übergebene ID der Membervariablen intKuchenId
zu
und ruft initKuchen
auf.KuchenDetailHandler.initKuchen()
erkennt, dass ein vorhandener Kuchen (ID > 0) in dieser Klasse vorliegt, und lädt ein Objekt KuchenBean
über KuchenZutatWorkerBean.findKuchenById
.ZutatDetailHandler.zutatSave
gibt "kuchendetail" zurück.JSF-Framework
rendert gemäß dieser Rückgabe die Seite "kuchendetail.xhtml".<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="4.0" xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd">
<navigation-rule>
<description>"kuchenDetail" führt immer zur Detail-Seite!</description>
<display-name>KuchenDetail-Seite</display-name>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>kuchendetail</from-outcome>
<to-view-id>/kuchendetail.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<description>"kuchenliste" führt immer zur Liste-Seite!</description>
<display-name>KuchenListe-Seite</display-name>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>kuchenliste</from-outcome>
<to-view-id>/kuchenliste.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<description>"zutatDetail" führt immer zur Detail-Seite!</description>
<display-name>ZutatDetail-Seite</display-name>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>zutatdetail</from-outcome>
<to-view-id>/zutatdetail.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
@Named(value="kuchenListeHandler")
@RequestScoped
public class KuchenListeHandler extends BaseHandler
{
Die Beans stecken alle im Request-Scope. Das hat den Nachteil dass bei jedem Request die benötigten Daten neu geladen werden.
Der Vorteil ist dass nach einem Speichern eines Kuchens und anschließendem Zurückspringen zur Kuchenliste ein neuer Request erfolgt
und dadurch die Kuchenliste mitsamt der Änderung neu eingeladen wird. Wäre die Kuchenliste (KuchenListeHandler
) in der Session,
dann müsste der geänderte Kuchen mühsam in der Liste aktualisiert werden (was von der Managed Bean aus nicht direkt möglich ist). KuchenDetailHandler.editKuchen
wird ein Seitenname "kuchendetail.xhtml" erzeugt,
und da es diese Seite gibt, wird sie automatisch aufgerufen. Dadurch werden die in "faces-config.xml" definierten Navigation Rules übergangen.
Man müsste also, damit die Konfiguration in meinem Beispiel genutzt wird, die Rückgaben der Methoden anders benennen. <navigation-rule>
<description>"kuchenliste" führt diesmal nur als Rückgabe der Action "kuchenDetailHandler.kuchenSave" zur Liste!</description>
<display-name>KuchenListe-Seite</display-name>
<navigation-case>
<from-action>#{kuchenDetailHandler.kuchenSave}</from-action>
<from-outcome>kuchenliste</from-outcome>
<to-view-id>/kuchenliste.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Hier greift die Regel nur für die Rückgabe "kuchenliste" der Methode de.hsrm.jakartaee.knauf.kuchenzutatfaces.KuchenDetailHandler.kuchenSave
.
Man beachte den EL-Ausdruck zur Referenzierung der Methode (leider ohne WTP-Unterstützung für die Codeergänzung). <navigation-rule>
<description>Der Link "navigationkuchenliste" führt zur Übersichtsseite!</description>
<display-name>Link "Zur Kuchenliste" auf kuchendetail.xhtml</display-name>
<from-view-id>/kuchendetail.xhtml</from-view-id>
<navigation-case>
<from-outcome>kuchenliste</from-outcome>
<to-view-id>/kuchenliste.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
Dies greift für den CommandLink "Zur Kuchenliste" auf "kuchendetail.xhtml" (eigentlich gilt die Regel für alle Links, deren Property "action"
auf "kuchenliste" steht). Die Quell-View wird im Element from-view-id
angegeben, hier also "/kuchendetail.xhtml".
@Alternative
versehen.
Per Default wird immer eine Instanz von "BeanX" erzeugt und injiziert. Deklariert man in "beans.xml" allerdings "BeanY" im "alternatives"-Element,
wird stattdessen eine Instanz dieser Bean erzeugt&<?xml version="1.0" encoding="UTF-8"?>
<beans version="4.0"
xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
bean-discovery-mode="annotated">
</beans>
Man beachte den "bean-discovery-mode", für den man sich entscheiden muss. Ich nutze hier "annotated", aber mein Beispiel klappt auch mit "none",
da ich keine speziellen CDI-Features nutze.de.hsrm.jakartaee.knauf.kuchenzutatfaces.KuchenZutatWorkerBean
.
Diese können wir mittels EJB-Injection einlesen. @EJB()
private KuchenZutatWorkerLocal kuchenZutatWorkerBean;
Da ein Zugriff über den Environment Naming Context nicht nötig ist, müssen weder in web.xml noch in jboss-web.xml EJB-Referenzen eingetragen sein <?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="urn:jboss:jakartaee:1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:jakartaee:1.0 https://www.jboss.org/schema/jbossas/jboss-web_15_0.xsd"
version="15.0">
<context-root>KuchenZutatJSFWeb</context-root>
</jboss-web>
public class BaseHandler
{
@EJB()
private KuchenZutatWorkerLocal kuchenZutatWorkerBean;
protected KuchenZutatWorkerLocal getKuchenZutatWorker()
{
return this.kuchenZutatWorkerBean;
}
}
<html xmlns="http://www.w3.org/1999/xhtml" ...> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" /> <title>...</title> </head>Diese Definition gilt nur für die Darstellung der Ergebnisseiten, aber nicht für das Auswerten von z.B. Formulareingaben. Jakarta Faces verwendet für deren Parsen "UTF-8" als Default-Encoding. Deshalb würden Umlaute falsch dargestellt werden.
f:view
-Element definiert:<f:view encoding="ISO-8859-1"/> ... </>
<h:inputHidden id="kuchenid" value="#{kuchenDetailHandler.kuchenId}"></h:inputHidden>
<f:param>
-Tag: <h:commandButton>
oder <h:commandLink>
können Werte an das Ziel übergeben werden.
Dies kann so aussehen (Link zum Bearbeiten einer Zutat auf "kuchendetail.jsp"): <h:commandLink id="kuchenedit" action="#{zutatDetailHandler.editZutat}"
actionListener="#{zutatDetailHandler.selectKuchenZutat}" value="Bearbeiten">
<f:param id="kuchenId" value="#{kuchenDetailHandler.kuchenId}"></f:param>
<f:param id="zutatId" value="#{zutatAktuell.id}"></f:param>
</h:commandLink>
"zutatAktuell" ist hier eine Variable im PageContext, die beim Befüllen des dataTable
aus der Zutatenliste gesetzt wurde. editZutat
hat keine Chance auf die Werte zuzugreifen. Deshalb muss
der Parameter manuell ausgewertet werden. Dazu wurde das "actionListener
"-Attribut gesetzt. Hier wird eine void
-Methode angegeben
die einen Parameter vom Typ "jakarta.faces.event.ActionEvent
" erwartet. public void selectKuchenZutat(ActionEvent event)
{
UIParameter component = (UIParameter) event.getComponent().findComponent("kuchenId");
this.intKuchenId = Integer.parseInt(component.getValue().toString());
UIParameter component = (UIParameter) event.getComponent().findComponent("zutatId");
this.intZutatId = Integer.parseInt(component.getValue().toString());
}
Es werden zwei Komponenten mit den Namen "kuchenId" und "zutatId" gesucht, die vom Typ jakarta.faces.component.UIParameter
sind.
Deren Werte werden ausgewertet und in Membervariablen der Klasse gesetzt. Diese sind zum Zeitpunkt des Aufrufs von editZutat
vorhanden. <context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
Dies führt in jedem HTML-Formular zu einem solchen Hidden Field: <input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="j_id1:j_id6" />
Der "value" dieses ViewState ist pro Seite innerhalb der aktuellen Session eindeutig (anscheinend wird pro aufgerufender Seite die letzte Zahl (im Beispiel also "6") hochgezählt). <context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
Dies führt in jedem HTML-Formular zu einem solchen Hidden Field (Beispiel aus "zutatdetail.jsp" bei neuer Zutat): <input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="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" />
Vorteil: Der Server muss den ViewState nicht selbst speichern.<h:outputText>
-Tags
scheinen ihren letzten Wert nicht mitzuführen. Dies führte dazu, dass meine Kuchen-Bearbeiten-Seite bei einem Eingabefelder zwar alle Felder mit den letzten Eingaben
füllte, das Textfeld "Kuchen-ID" aber den Wert aus dem uninitialisierten KuchenDetailHandler
holte und ausgab.<f:param>
-Tags (siehe "Weg 2" im vorherigen Kapitel). KuchenDetailHandler
im Request-Scope steckte wurde er natürlich
neu erzeugt und konnte deshalb keine Zutaten zurückliefern. Deshalb konnte die JSF-Implementation die Zutat nicht ermitteln und
setzt ohne irgendwelche Fehlermeldungen die Zutat-ID auf "0", was einer neuen Zutat entsprach! "setKuchenId
" des KuchenDetailHandler
wurde nicht aufgerufen, also keine Chance einzugreifen. <f:view>
-Tag das beforePhase
-Attribut gesetzt wird: <f:view beforePhase="#{kuchenDetailHandler.beforePhase}">
KuchenDetailHandler.beforePhase
sucht in den Request-Parametern nach allem was mit ":kuchenId" endet (JSF setzt
die komplette Komponentenhierarchie in den Namen des Request-Parameters, in der Zutatenliste sieht das so aus: tablezutaten:0:formzutatedit:kuchenId
für die Zutat in Zeile 0, tablezutaten:1:formzutatedit:kuchenId
für die Zutat in Zeile 1. Deshalb habe ich hier keine Chance auf den
exakten Namen der Komponente zu prüfen) public void beforePhase (javax.faces.event.PhaseEvent e)
{
//Nur etwas machen in der Phase "ApplyRequestValues"!
if (e.getPhaseId().equals( PhaseId.APPLY_REQUEST_VALUES))
{
Map<String, String> mapRequest = e.getFacesContext().getExternalContext().getRequestParameterMap();
for (Entry<String, String> entryAktuell : mapRequest.entrySet() )
{
String strKey = entryAktuell.getKey();
if (strKey.endsWith(":kuchenId"))
{
this.intKuchenId = Integer.parseInt(entryAktuell.getValue());
}
}
}
}
Das bedingt leider eine Änderung an der Parameterversorgung des Zutat-Detail-Links: <f:param>
-Tags kann ich nicht als Request-Parameter
parsen! Man könnte zwar den gesamten Komponentenbaum der aktuellen View durchlaufen, aber damit wüßte man immer noch nicht, welches Formular gerade abgeschickt wird,
und woher deshalb die Kuchen-ID kommt. Deshalb verwende ich ein Hidden Field, und suche seinen Wert aus dem ServletRequest: <h:form id="formzutatedit">
<h:inputHidden id="kuchenId" value="#{kuchenDetailHandler.kuchenId}"></h:inputHidden>
<h:commandLink id="kuchenedit" action="#{zutatDetailHandler.editZutat}"
actionListener="#{zutatDetailHandler.selectKuchenZutat}" value="Bearbeiten">
<f:param id="zutatId" value="#{zutatAktuell.id}"></f:param>
</h:commandLink>
</h:form>
Dieses Hidden Field wird zwar dank Value Binding in den KuchenDetailHandler
zurückgeschrieben, das stört uns allerdings nicht weiter.
Wichtig ist, dass beim Postback eines jeden Formulars schon vor der Validierungsphase die Kuchen-ID ermittelt werden kann.ZutatDetailHandler
kommt die KuchenId in diesem konkreten Fall nicht mehr als UIParameter
an, sondern in einem HTML-Hidden Field. Deshalb muss die Logik in ZutatDetailHandler.selectKuchen
(diese Methode wird auch
aus selectKuchenZutat
heraus aufgerufen) erweitert werden: public void selectKuchen(ActionEvent event)
{
String strValue = null;
if (event.getComponent().findComponent("kuchenId").getClass().equals(UIParameter.class) )
{
UIParameter component = (UIParameter) event.getComponent().findComponent("kuchenId");
strValue = component.getValue().toString();
}
else if (event.getComponent().findComponent("kuchenId").getClass().equals(HtmlInputHidden.class) )
{
HtmlInputHidden component = (HtmlInputHidden) event.getComponent().findComponent("kuchenId");
strValue = component.getValue().toString();
}
else
{
throw new FacesException ("Keine Komponente kuchenId gefunden!");
}
this.intKuchenId = Integer.parseInt(strValue);
}
Hier wird also die Kuchen-ID in zwei unterschiedlichen Komponenten-Typen gesucht. @Inject
-Annotation die Instanz des Handlers injiziert:
import javax.inject.Inject;
...
@Inject
private KuchenListeHandler kuchenListeHandler;
public String deleteKuchen()
{
...
kuchenListeHandler.clearKuchenliste();
return "kuchenliste";
}
<h:inputText label="Name" id="name" value="#{kuchenDetailHandler.name}" required="true">
<f:validateLength minimum="5"></f:validateLength>
</h:inputText>
Entstehen Fehlermeldungen beim Validieren dieser Komponente, können sie an beliebiger Stelle der Seite ausgegeben werden:
<h:message for="name"></h:message>
Durch das Attribut for
wird der Name der Komponente angegeben deren Validierungsfehler hier ausgegeben werden sollen.
Wird ein Attribut label
des h:input
angegeben, so enthält die Fehlermeldung diesen Namen, ansonsten den internen Namen der Komponente in einer eher häßlichen Darstellung. beforePhase
aus dem Hidden Field zieht! Das ist nur durch eine beforePhase
zu umgehen, siehe Abschnitt ViewState/LifeCycle.form
stecken wie die eigentlichen Eingabefelder. Grund: bei einem Klick auf den Link wird das gesamte Formular validiert, und deshalb
könnte ein nicht ausgefülltes Eingabefeld einen Klick auf den Link verbieten. h:form
, h:inputHidden
, h:inputText
usw.).
Dadurch ist das Element in Fehlermeldungen oder beim Auswerten des Requests einfacher zuzuordnen.