Kuchen-Zutat-Beispiel mit Jakarta Faces (JakartaEE10)


Inhalt:

Vorbereitung
Überblick
Use Cases
faces-config.xml
"beans.xml"
EJB-Injection
Encoding
Parameterübergabe
ViewState/LifeCycle
Phase Event
Zugriff auf andere Manged Bean
Validierung
Weitere Design-Tips

Für WildFly 33 und JakartaEE 10: Dieses Beispiel stellt das KuchenZutat-Beispiel komplett auf JSF um und kämpft dabei mit einer Reihe von Problemen.

Hier gibt es das Projekt als EAR-Export-Datei: KuchenZutatJSF.ear.
Nach dem Import muss man die JSF-Facet dem Projekt zufügen, siehe JSF-Facet zu bestehendem Projekt zufügen


Vorbereitung

Die Definition der Server-Runtime in Eclipse fügt die Jakarta Faces-Libraries, die WildFly bündelt (Datei "%WILDFLY_HOME%\modules\system\layers\base\jakarta\faces\impl\main\jakarta.faces-4.0.7.jar"), nicht dem BuildPath des Projekts hinzu. Um dies zu lösen, gäbe es drei Möglichkeiten: In den Preferences unter "Server" => "Runtime Environments" => "Default Classpath Entries" wird oben die "WildFly 27+ Runtime" gewählt und auf "Add" geklickt:
Server Runtime (1)

Man wählt den Typ "JBoss module":
Server Runtime (2)

Als "Module ID" wird jakarta.faces.impl angegeben:
Server Runtime (3)
Der JBoss Tools-Plugin löst dies zum Pfad "%WILDFLY_HOME%\modules\system\layers\base\jakarta\faces\impl\main\" und der Datei "jakarta.faces-4.0.7.jar" auf.

Das Ergebnis sieht so aus:
Server Runtime (4)

Auch im Build Path des Projekts taucht die Datei jetzt auf:
Server Runtime (5)

Überblick

Die Anwendung enthält vier Seiten:

Es gibt drei Managed Beans:
Die Navigation sieht so aus (alles über parameterlose Action-Methoden abgebildet):


Use Cases

Da die Umsetzung der Anwendungslogik sehr komplex ist, versuche ich hier, die Abläufe der wichtigsten Use Cases zu zitieren. Leider habe ich kein brauchbares freies Sequenz-Diagramm-Tool gefunden, deshalb muss ich hier bei der textuellen Beschreibung bleiben.

Use Case "Kuchen anlegen"


Use Case "Kuchen bearbeiten"
Dieser Use-Case ist dem letzten sehr ähnlich, nur der Start der Aktion sowie das Initialisieren der kuchenAktuell-Membervariablen ist unterschiedlich.
Use Case "Zutat bearbeiten"
Ausgehend von der Kuchenliste wird zuerst die Kuchen-Bearbeiten-Seite aufgerufen (man könnte diesen Use-Case also als "extends" von "Kuchen bearbeiten" modellieren). Deshalb ist der gesamte Schritt 1 und Unterschritte identisch mit "Kuchen bearbeiten". Allerdings folgen hier die Schritte für das Aufbauen der Zutatenliste.
Der Rest des Use Cases entspricht wieder den Schritten 1.3.1 bis 1.3.3.2 dieses Use Cases.


Die anderen drei Use Cases "Kuchen löschen", "Zutat anlegen" und "Zutat löschen" haben keine schlimmen Besonderheiten und werden deshalb nicht im Detail erklärt.

faces-config.xml

"faces-config.xml" sieht so aus nachdem wir damit fertig sind (es sind nur drei Navigation-Rules eingetragen, da die ManagedBeans alle über CDI-Annotations definiert werden):
<?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> 

Die Managed Beans sind alle über CDI-Annotations definiert:
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).

Die Navigation Rules sind eher simpel gehalten: jede Action-Methode die als Rückgabe "kuchendetail" hat wird automatisch zur View "kuchendetail.xhtml" weitergeleitet, und analog für die Rückgaben "zutatdetail" und "kuchenliste". Man beachte, dass im Vergleich zu meinem JSF 1.2-Beispiel gemäß XSD in jeder "navigation-rule" eine "from-view-id" angegeben werden muss. Hier habe ich der Einfachheit halber immer "*" eingetragen, da es keinen Grund gibt, unterschiedliche Ziele je nach Quellseite anzunavigieren.


Seit JSF 2.0 gibt es "Implicit Navigation Rules":
JSF versucht, die Rückgabe einer Action in einer JSF-Seite aufzulösen, aus der Rückgabe "kuchendetail" von 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.


Jede "Page" ist mit einer echten XHTML-Seite verbunden. Diese wird in der Property "Page Path" angegeben.
Für einen Link können wir in der Property "From Outcome" angeben, für welchen Rückgabewert einer Action-Methode diese Regel gelten soll.

Info: im obigen Beispiel gelten die Navigation Rules für die Rückgabe aller Actions. Wenn man eine Regel nur für eine einzelne Methode angeben will, könnte das so aussehen:
	<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).

Analog sieht es aus, wenn man auf einer XHTML-Seite direkt einen Link zu einer anderen View baut.
	<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".

"beans.xml"

Seit JavaEE6 ist ein weiterer Deployment Deskriptor namens "beans.xml" nötig. Dieser wird für CDI (Contexts and Dependency Injection) benötigt, was eine Erweiterung der Injection-Fähigkeiten von früheren JavaEE-Versionen ist (wo man in z.B. einem Servlet nur EJBs und MDBs injizieren konnte).

Mittels dieser Datei werden keine Beans deklariert, so wie man das z.B. in "ejb-jar.xml" machen könnte. Die Datei dient dazu, das Verhalten von vorhandenen (und vom Container gefundenen) Beans zu ändern. Beispiele sind "interceptors", "alternatives" und "decorators".
Grundlagen von CDI:
https://jakarta.ee/learn/docs/jakartaee-tutorial/current/cdi/cdi-basic/cdi-basic.html
"beans.xml": https://jakarta.ee/learn/docs/jakartaee-tutorial/current/cdi/cdi-adv/cdi-adv.html
Die Spezifikation ist zu finden unter https://jakarta.ee/specifications/cdi/

"alternatives": Beispiel: es soll eine Instanz von "BeanX" in eine Variable (vom Typ "BeanX") eines Servlets injiziert werden. Von dieser "BeanX" (keine EJB, sondern eine beliebige Java-Klasse, also eine Bean) gibt es außerdem eine Subklasse "BeanY", die die Methoden von "BeanX" erweitert um z.B. Code für einen Test. "BeanY" ist mit der Annotation @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

"interceptor": Ein Interceptor kapselt Aufrufe von Methoden einer injizierten Bean, z.B. fürs Logging.

"decorator": ähnlich wie eine "alternative", aber hier schaltet der User nicht zwischen zwei Beans um, sondern kapselt einen "Decorator" um den Aufruf der Bean-Methode, der zusätzliche Logik macht


Im einfachsten Fall ist diese Datei leer:
&<?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.


EJB-Injection

Alle drei Managed Beans benötigen die Stateless Session Bean de.hsrm.jakartaee.knauf.kuchenzutatfaces.KuchenZutatWorkerBean. Diese können wir mittels EJB-Injection einlesen.
Das sieht so aus:
  @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
"jboss-web.xml" ist eigentlich nicht nötig, ich habe sie nur der Vollständigkeit halber angelegt. Sie sieht so aus:
<?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> 


Um die EJBs nur an einer Stelle zu deklarieren, habe ich mir eine Basisklasse für alle meine Managed Beans deklariert, die die KuchenZutatWorkerBean per Injection holt und sie in einer privaten Variable speichert. Der Zugriff erfolgt nur durch eine get-Methode. Es wäre durch diese Codestruktur möglich, nach Umstellung nur einer einzigen Klasse und Änderung zweier Deployment Deskriptoren auf händischen JNDI-Lookup zu wechseln (ein Beispiel hierfür findet sich im Vorjahres-Beispiel, in dem die JSF-Library ausgetauscht werden musste und deshalb JBoss Injection nicht unterstützte).
public class BaseHandler
{
  @EJB()
  private KuchenZutatWorkerLocal kuchenZutatWorkerBean;
  
  protected KuchenZutatWorkerLocal getKuchenZutatWorker()
  {
    return this.kuchenZutatWorkerBean;
  }
}


Encoding

Meine Faces-Seiten (bzw. die daraus erzeugten HTML-Seiten) definieren (weil das Beispiel sehr alt ist) das Encoding "ISO-8859-1":
<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.
Lösung: das Encoding wird auch auf dem f:view-Element definiert:
<f:view encoding="ISO-8859-1"/>
  ...
</>




Parameterübergabe

An einigen Stellen muss beim Sprung von einer View zur anderen ein Parameter übergeben werden. Dies kann auf zwei Wegen geschehen (jeweils mit Vor- und Nachteilen).

Weg 1: Hidden Field:
Aus "kuchendetail.jsp" im Formular für das Bearbeiten des aktuellen Kuchens:
<h:inputHidden id="kuchenid" value="#{kuchenDetailHandler.kuchenId}"></h:inputHidden> 

Vorteil: der Wert wird beim Generieren der JSP aus der aktuellen Instanz des KuchenDetail-Handlers gelesen und beim Absenden des Requests dort auch wieder hineingeschrieben.

Nachteil: Falls die Zielseite eine andere Seite ist, dann wird der Wert aus dem Parameter trotzdem in den KuchenDetail-Handler geschrieben. Deshalb nicht allzu geeignet für z.B. die Übergabe einer Kuchen-ID in der Zutatenliste eines Kuchens, wenn das Ziel die Zutatenseite ist.

Fazit: einfaches, aber unflexibles Verfahren, dass nur innerhalb einer Managed Bean funktioniert (zum Beispiel in "kuchendetail.jsp" beim Klick auf "OK").

Weg 2: <f:param>-Tag:
Innerhalb eines <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.

Vorteil: wird an die Zielseite geschickt.

Nachteil: Keinerlei Automatismus. Die Action-Methode 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.
Diese Methode könnte so aussehen:
	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.

Mehr Infos dazu:
http://balusc.blogspot.com/2006/06/communication-in-jsf.html
(gemäß dieser Seite ist es scheinbar doch möglich, in der Action-Methode auf "f:param"-Parameter zuzugreifen, das wird bei Gelegenheit ausprobiert ;-) )


ViewState/LifeCycle

Die in diesem Abschnitt beschriebenen Informationen sind sehr wichtig, um den Ablauf in einer JSF-Anwendung zu verstehen, und um sich scheinbare Ungereimtheiten gerade beim Bean-Scope "Request" (also: Bean bei jedem Request neu erzeugen, keinerlei Zustandsspeichern zwischen Aufrufen) zu erklären!
Es sei auf das Kapitel
2.2 Standard Request Processing Lifecycle Phases in der Jakarta Faces-4.0-Spezifikation verwiesen.

Das folgende Bild ist der Spezifikation entnommen und bietet einen Überblick über den Lifecyle:
Lifecycle
Siehe auch: http://www.fh-wedel.de/~si/seminare/ws06/Ausarbeitung/10.JavaServerFaces/jsf2.html#jsfl

Erster Seitenaufruf:
Beim ersten Aufruf einer Seite springt das JSF-Framework direkt in die "Render Response"-Phase, hier gibt es keine besonderen Fallstricke.

Postback:
Beim sogenannten "Postback" einer Seite (also beim Abschicken irgendeines Formulars) ist der Ablauf folgender:
Aus diesen Phasen ergeben sich einige große Besonderheiten, die vor allem zuschlagen, wenn man Backing Beans im Request-Scope verwendet!
Es gäbe zwei Lösungen des Dilemmas:




WEITERMACHEN WEITERMACHEN WEITERMACHENWEITERMACHEN


Phase Event

Das Einklinken in die Verarbeitungsphasen von JSF geschieht, indem im <f:view>-Tag das beforePhase-Attribut gesetzt wird:
<f:view beforePhase="#{kuchenDetailHandler.beforePhase}"> 

Die Methode 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)
Der Code der Methode sieht so aus:
  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.

Diese schicke Lösung hat einen Nachteil: im 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.


Zugriff auf andere Manged Bean

An einigen Stellen des Beispiels ergeben sich auch dem Weg durch die Beans Inkonsistenzen: Beispielsweise wird das Löschen eines Kuchens in "KuchenDetailHandler.deleteKuchen" durchgeführt. Danach soll zurück zu "kuchenliste.xhtml" gesprungen werden. Allerdings würde diese Seite in diesem Fall immer noch den eigentlich schon gelöschten Kuchen anzeigen.
Grund: Nach dem Klick auf "kuchenliste.xhtml" -> "Kuchen bearbeiten" wird der "beforePhase"-Handler des "KuchenListeHandler" aufgerufen. Und das führt dazu, dass dort die Kuchenliste bereits initialisiert wurde. Nach dem Löschen des Kuchens enthält diese Liste ihn aber immer noch. Deshalb muss beim Löschen der Kuchen in der Bean entfernt werden.
Die gleiche Problematik besteht im "ZutatDetailHandler": dort kommt man von "kuchendetail.xhtml", d.h. der aktuelle Kuchen wurde bereits eingeladen und enthält dadurch noch nicht die geänderte/gelöschte Zutat.

Lösung für beides: auf den Handler zugreifen und die dort gecachten Daten zurücksetzen. Dazu wird mittels @Inject-Annotation die Instanz des Handlers injiziert:
import javax.inject.Inject;
...
  @Inject
  private KuchenListeHandler kuchenListeHandler;

Anschließend kann nach dem Speichern des Kuchens die Kuchenliste im Handler zurückgesetzt werden:
  public String deleteKuchen()
  {
    ...
    
    kuchenListeHandler.clearKuchenliste();
    
    return "kuchenliste";
  }


Validierung

Im Beispiel sind alle Eingabefelder als Pflichtelement deklariert (Attribut "required" setzen). Für den Kuchen ist die minimale Länge auf 5 Zeichen gesetzt:
	<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.

ACHTUNG: wenn man auf "kuchendetail.xhtml" beim Bearbeiten eines bestehenden Kuchens einen Validierungsfehler hat, dann wird die Kuchen-ID NUR in die Managed Bean gesetzt, wenn man sie in der beforePhase aus dem Hidden Field zieht! Das ist nur durch eine beforePhase zu umgehen, siehe Abschnitt
ViewState/LifeCycle.


Weitere Design-Tips

Formulare sollten möglichst klein gehalten werden, z.B. sollte ein Navigationslink nicht im gleichen 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.

IDs: Alle Formular-Elemente sollten mit IDs versehen werden (h:form, h:inputHidden, h:inputText usw.). Dadurch ist das Element in Fehlermeldungen oder beim Auswerten des Requests einfacher zuzuordnen.



Stand 28.09.2024
Historie:
28.09.2024: Erstellt aus JavaEE8-Beispiel und angepaßt an JakartaEE10 und WildFly 33