Skip to content

2. Java Server Faces

Wir werden nun das Java Server Faces-Framework vorstellen. Es wird Version 2 verwendet, aber die Beispiele veranschaulichen in erster Linie Funktionen aus Version 1. Wir werden nur die Funktionen von Version 2 behandeln, die für die nachfolgende Beispielanwendung erforderlich sind.

2.1. Die Rolle von JSF in einer Webanwendung

Betrachten wir zunächst die Rolle von JSF bei der Entwicklung einer Webanwendung. Meistens basiert diese auf einer mehrschichtigen Architektur wie der folgenden:

  • Die [Web-]Schicht ist die Schicht, die mit dem Benutzer der Webanwendung in Kontakt steht. Der Benutzer interagiert mit der Webanwendung über Webseiten, die von einem Browser angezeigt werden. In dieser Schicht befindet sich JSF, und zwar ausschließlich in dieser Schicht;
  • Die [Business]-Schicht implementiert die Geschäftsregeln der Anwendung, wie beispielsweise die Berechnung eines Gehalts oder einer Rechnung. Diese Schicht nutzt Daten vom Benutzer über die [Web]-Schicht und aus dem DBMS über die [DAO]-Schicht.
  • wobei die [DAO]-Schicht (Data Access Objects), die [JPA]-Schicht (Java Persistence API) und der JDBC-Treiber den Zugriff auf die DBMS-Daten verwalten. Die [JPA]-Schicht dient als ORM (Object-Relational Mapper). Sie fungiert als Brücke zwischen den von der [DAO]-Schicht verwalteten Objekten und den Zeilen und Spalten der Daten in einer relationalen Datenbank;
  • Die Integration dieser Schichten kann mithilfe eines Spring-Containers oder EJB3 (Enterprise JavaBeans) erreicht werden.

Die unten aufgeführten Beispiele zur Veranschaulichung von JSF verwenden nur eine einzige Schicht, die [Web]-Schicht:

Sobald Sie die Grundlagen von JSF beherrschen, werden wir mehrschichtige Java-EE-Anwendungen erstellen.

2.2. Das JSF-MVC-Entwicklungsmodell

JSF implementiert das MVC-Architekturmuster (Model–View–Controller) wie folgt:

Diese Architektur implementiert das MVC-Entwurfsmuster (Model, View, Controller). Die Verarbeitung einer Client-Anfrage erfolgt in vier Schritten:

  1. Anfrage – Der Client-Browser sendet eine Anfrage an den Controller [Faces Servlet]. Der Controller verarbeitet alle Client-Anfragen. Er ist der Einstiegspunkt der Anwendung. Dies ist das C in MVC,
  2. Verarbeitung – der C-Controller verarbeitet diese Anfrage. Dabei wird er von anwendungsspezifischen Ereignisbehandlungsroutinen unterstützt [2a]. Diese Routinen benötigen unter Umständen Unterstützung durch die Geschäftslogik [2b]. Sobald die Client-Anfrage verarbeitet wurde, kann dies verschiedene Antworten auslösen. Ein klassisches Beispiel ist:
    • eine Fehlerseite, wenn die Anfrage nicht korrekt verarbeitet werden konnte;
    • ansonsten eine Bestätigungsseite,
  3. Navigation – Der Controller wählt die Antwort (= Ansicht) aus, die an den Client gesendet werden soll. Die Auswahl der an den Client zu sendenden Antwort umfasst mehrere Schritte:
    • Auswahl des Facelets, das die Antwort generiert. Dies wird als V-Ansicht bezeichnet, das V in MVC. Diese Wahl hängt in der Regel vom Ergebnis der Ausführung der vom Benutzer angeforderten Aktion ab;
    • die Versorgung dieses Facelets mit den Daten, die es zur Generierung dieser Antwort benötigt. Tatsächlich enthält diese Antwort meist Informationen, die vom Controller berechnet wurden. Diese Informationen bilden das sogenannte M-Modell der Ansicht, das M in MVC;

Schritt 3 besteht daher darin, eine Ansicht V auszuwählen und das dafür erforderliche Modell M zu erstellen.

  1. Antwort – Der Controller C weist das ausgewählte Facelet an, sich selbst darzustellen. Das Facelet verwendet das vom Controller C vorbereitete Modell M, um die dynamischen Teile der Antwort zu initialisieren, die es an den Client senden muss. Die genaue Form dieser Antwort kann variieren: Es kann sich um einen HTML-Stream, eine PDF-Datei, eine Excel-Datei usw. handeln.

In einem JSF-Projekt:

  • ist der Controller C das Servlet [javax.faces.webapp.FacesServlet]. Dieses befindet sich in der Bibliothek [javaee.jar],
  • die V-Ansichten werden durch Seiten implementiert, die die Facelets-Technologie nutzen,
  • die M-Modelle und Ereignisbehandler werden durch Java-Klassen implementiert, die oft als „Backing Beans“ oder einfach als Beans bezeichnet werden.

Lassen Sie uns nun die Beziehung zwischen der MVC-Webarchitektur und der Schichtenarchitektur klären. Es handelt sich um zwei unterschiedliche Konzepte, die manchmal verwechselt werden. Betrachten wir eine einschichtige JSF-Webanwendung:

Wenn wir die [Web-]Schicht mit JSF implementieren, erhalten wir zwar eine MVC-Webarchitektur, aber keine mehrschichtige Architektur. Hier übernimmt die [Web-]Schicht alles: Darstellung, Geschäftslogik und Datenzugriff. Bei JSF übernehmen die Beans diese Aufgaben.

Betrachten wir nun eine mehrschichtige Webarchitektur:

Die [Web-]Schicht kann ohne Framework und ohne Befolgung des MVC-Modells implementiert werden. Wir haben dann eine mehrschichtige Architektur, aber die Web-Schicht implementiert das MVC-Modell nicht.

In MVC haben wir gesagt, dass das M-Modell das der V-Ansicht ist, d. h. die Menge der von der V-Ansicht angezeigten Daten. Oft wird eine andere Definition des M-Modells in MVC angegeben:

Viele Autoren sind der Ansicht, dass das, was rechts von der [Web]-Schicht liegt, das M-Modell von MVC bildet. Um Unklarheiten zu vermeiden, beziehen wir uns auf:

  • das Domänenmodell, wenn wir uns auf alles rechts von der [Web]-Schicht beziehen,
  • das View-Modell, wenn wir uns auf die von einer Ansicht V angezeigten Daten beziehen.

Im Folgenden bezieht sich der Begriff „M-Modell“ ausschließlich auf das Modell einer V-Ansicht.

2.3. Beispiel mv-jsf2-01: die Komponenten eines JSF-Projekts

Die ersten Beispiele beschränken sich auf die mit JSF 2 implementierte Webschicht:

Sobald die Grundlagen behandelt wurden, werden wir komplexere Beispiele mit mehrschichtigen Architekturen untersuchen.

2.3.1. Projektgenerierung

Wir erstellen unser erstes JSF2-Projekt mit NetBeans 7.

  
  • Erstellen Sie in [1] ein neues Projekt,
  • wählen Sie in [2] die Kategorie [Maven] und den Projekttyp [Webanwendung] aus,
  • Geben Sie in [3] den übergeordneten Ordner für das neue Projekt an,
  • Geben Sie in [4] einen Namen für das Projekt ein,
  • wählen Sie in [5] einen Server aus. Mit NetBeans 7 können Sie zwischen den Servern Apache Tomcat und GlassFish wählen. Der Unterschied zwischen den beiden besteht darin, dass GlassFish EJBs (Enterprise Java Beans) unterstützt, Tomcat hingegen nicht. Unsere JSF-Beispiele verwenden keine EJBs. Sie können hier also einen beliebigen Server auswählen,
  • in [6] die Java EE 6 Web-Version auswählen,
  • in [7] das generierte Projekt.

Sehen wir uns die Projektelemente an und erklären wir die jeweilige Funktion.

  • In [1]: die verschiedenen Zweige des Projekts:
    • [Web Pages]: enthält die Webseiten (.xhtml, .jsp, .html), Ressourcen (Bilder, verschiedene Dokumente), die Konfiguration der Webschicht und die Konfiguration des JSF-Frameworks;
    • [Quellpakete]: die Java-Klassen des Projekts;
    • [Abhängigkeiten]: die vom Projekt benötigten und vom Maven-Framework verwalteten .jar-Archive;
    • [Java-Abhängigkeiten]: die vom Projekt benötigten .jar-Archive, die nicht vom Maven-Framework verwaltet werden;
    • [Projektdateien]: Maven- und NetBeans-Konfigurationsdateien,
  • in [2]: der Zweig [Webseiten],

Er enthält die folgende Seite [index.jsp]:


<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
   "http://www.w3.org/TR/HTML4/loose.dtd">
 
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>Hello World!</h1>
    </body>
</html>

Dies ist eine Webseite, die den Text „Hello World“ in großer Schrift anzeigt.

Die Datei [META-INF/context.xml] sieht wie folgt aus:


<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/mv-jsf2-01"/>

Zeile 2 gibt an, dass der Anwendungskontext (oder dessen Name) /mv-jsf2-01 lautet. Das bedeutet, dass die Webseiten des Projekts über eine URL der Form http://machine:port/mv-jsf2-01/page aufgerufen werden. Der Kontext ist standardmäßig der Projektname. Wir müssen diese Datei nicht ändern.

  • In [3], dem Zweig [Source Packages],

Dieser Zweig enthält den Quellcode für die Java-Klassen des Projekts. Hier gibt es keine Klassen. NetBeans hat ein Standardpaket generiert, das gelöscht werden kann [4].

  • in [5], dem Zweig [Dependencies],

Dieser Zweig zeigt alle Bibliotheken an, die vom Projekt benötigt und von Maven verwaltet werden. Alle hier aufgeführten Bibliotheken werden automatisch von Maven heruntergeladen. Aus diesem Grund benötigt ein Maven-Projekt einen Internetzugang. Die heruntergeladenen Bibliotheken werden lokal gespeichert. Wenn ein anderes Projekt eine Bibliothek benötigt, die lokal bereits vorhanden ist, wird sie nicht erneut heruntergeladen. Wir werden sehen, dass diese Liste der Bibliotheken sowie die Repositorys, in denen sie zu finden sind, in der Konfigurationsdatei des Maven-Projekts definiert sind.

  • in [6] die vom Projekt benötigten Bibliotheken, die nicht von Maven verwaltet werden,
  • in [7] die Maven-Projektkonfigurationsdateien:
    • [nb-configuration.xml] ist die NetBeans-Konfigurationsdatei. Wir werden uns damit nicht befassen.
    • [pom.xml]: die Maven-Konfigurationsdatei. POM steht für Project Object Model. Manchmal müssen wir diese Datei möglicherweise direkt bearbeiten.

Die generierte [pom.xml]-Datei sieht wie folgt aus:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  <properties>
    <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
          <compilerArguments>
            <endorseddirs>${endorsed.dir}</endorseddirs>
          </compilerArguments>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.1.1</version>
        <configuration>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>2.1</version>
        <executions>
          <execution>
            <phase>validate</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
              <outputDirectory>${endorsed.dir}</outputDirectory>
              <silent>true</silent>
              <artifactItems>
                <artifactItem>
                  <groupId>javax</groupId>
                  <artifactId>javaee-endorsed-api</artifactId>
                  <version>6.0</version>
                  <type>jar</type>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
 
</project>
  • Die Zeilen 5–8 definieren das Java-Artefakt, das vom Maven-Projekt erstellt wird. Diese Informationen stammen aus dem Assistenten, der bei der Erstellung des Projekts verwendet wurde:

Ein Maven-Artefakt wird durch vier Eigenschaften definiert:

  • [groupId]: Eine Angabe, die einem Paketnamen ähnelt. Beispielsweise haben die Bibliotheken des Spring-Frameworks die groupId=org.springframework, während die des JSF-Frameworks die groupId=javax.faces haben,
  • [artifactId]: der Name des Maven-Artefakts. In der Gruppe [org.springframework] finden wir die folgenden artifactIDs: spring-context, spring-core, spring-beans, ... In der Gruppe [javax.faces] finden wir die artifactId jsf-api,
  • [version]: die Versionsnummer des Maven-Artefakts. So hat das Artefakt org.springframework.spring-core die folgenden Versionen: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
  • [packaging]: das Format des Artefakts, meist war oder jar.

Unser Maven-Projekt generiert daher ein [war] (Zeile 8) in der Gruppe [istia.st] (Zeile 5) mit dem Namen [mv-jsf2-01] (Zeile 6) und der Version [1.0-SNAPSHOT] (Zeile 7). Diese vier Informationen müssen ein Maven-Artefakt eindeutig identifizieren.

In den Zeilen 17–24 sind die Abhängigkeiten des Maven-Projekts aufgeführt, d. h. die Liste der vom Projekt benötigten Bibliotheken. Jede Bibliothek wird durch die vier Angaben (groupId, artifactId, version, packaging) definiert. Fehlt die Angabe zur Verpackung, wie hier, wird die jar-Verpackung verwendet. Eine weitere Information wird hinzugefügt: scope, das angibt, in welchen Phasen des Projektlebenszyklus die Bibliothek benötigt wird. Der Standardwert ist compile, was bedeutet, dass die Bibliothek sowohl für die Kompilierung als auch für die Ausführung benötigt wird. Der Wert provided bedeutet, dass die Bibliothek während der Kompilierung benötigt wird, nicht jedoch während der Ausführung. Hier wird sie zur Laufzeit vom Tomcat 7-Server bereitgestellt.

2.3.2. Ausführen des Projekts

Wir führen das Projekt aus:

In [1] wird das Maven-Projekt ausgeführt. Der Tomcat-Server wird dann gestartet, falls er noch nicht läuft. Außerdem wird ein Browser gestartet und die URL des Projektkontexts aufgerufen [2]. Da kein Dokument angefordert wird, wird die Seite index.html, index.jsp oder index.xhtml verwendet, sofern sie vorhanden ist. In diesem Fall handelt es sich um die Seite [index.jsp].

2.3.3. Das Dateisystem eines Maven-Projekts

  • [1]: Das Dateisystem des Projekts befindet sich auf der Registerkarte [Dateien],
  • [2]: Die Java-Quelldateien befinden sich im Ordner [src/main/java],
  • [3]: Die Webseiten befinden sich im Ordner [src/main/webapp],
  • [4]: Der Ordner [target] wird beim Erstellen des Projekts angelegt,
  • [5]: Hier hat der Projekt-Build ein Archiv [mv-jsf2-01-1.0-SNAPSHOT.war] erstellt. Dies ist das Archiv, das vom Tomcat-Server ausgeführt wurde.

2.3.4. Konfigurieren eines Projekts für JSF

Unser aktuelles Projekt ist kein JSF-Projekt. Es fehlen die JSF-Framework-Bibliotheken. Um das aktuelle Projekt in ein JSF-Projekt umzuwandeln, gehen Sie wie folgt vor:

  • Rufen Sie in [1] die Projekteigenschaften auf,
  • wählen Sie in [2] die Kategorie [Frameworks] aus,
  • fügen Sie unter [3] ein Framework hinzu,
  • Wählen Sie in [4] „Java Server Faces“ aus,
  • In [5] bietet NetBeans Version 2.1 des Frameworks an. Akzeptieren Sie diese,
  • in [6] wird das Projekt dann um neue Abhängigkeiten erweitert.

Die Datei [pom.xml] wurde aktualisiert, um diese neue Konfiguration widerzuspiegeln:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  ...
  <dependencies>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    ...
  </build>
  <repositories>
    <repository>
      <URL>http://download.java.net/maven/2/</URL>
      <id>jsf20</id>
      <layout>default</layout>
      <name>Repository for library Library[jsf20]</name>
    </repository>
    <repository>
      <URL>http://repo1.maven.org/maven2/</URL>
      <id>jstl11</id>
      <layout>default</layout>
      <name>Repository for library Library[jstl11]</name>
    </repository>
  </repositories>
</project>

Zeilen 14–33: Es wurden neue Abhängigkeiten hinzugefügt. Maven lädt diese automatisch herunter. Es bezieht sie aus sogenannten Repositorys. Standardmäßig wird das Central Repository verwendet. Zusätzliche Repositorys können mithilfe des <repository>-Tags hinzugefügt werden. Hier wurden zwei Repositorys hinzugefügt:

  • Zeilen 46–51: ein Repository für die JSF 2-Bibliothek,
  • Zeilen 52–57: ein Repository für die JSTL 1.1-Bibliothek.

Das Projekt wurde außerdem um eine neue Webseite erweitert:

Die Seite [ index.HTML] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

Hier sehen wir eine XML-Datei (Zeile 1). Sie enthält HTML-Tags, jedoch im XML-Format. Dies wird als XHTML bezeichnet. Die Technologie, die zur Erstellung von Webseiten mit JSF 2 verwendet wird, heißt Facelets. Daher wird die XHTML-Seite manchmal auch als Facelet-Seite bezeichnet.

In den Zeilen 3–4 wird das <html>-Tag mit XML-Namespaces (xmlns=XML-Namespace) definiert.

  • Zeile 3 definiert den Haupt-Namespace http://www.w3.org/1999/xhtml,
  • Zeile 4 definiert den Namespace http://java.sun.com/jsf/html für HTML-Tags. Diesen Tags wird, wie durch xmlns:h angegeben, das Präfix h: vorangestellt. Diese Tags finden sich in den Zeilen 5, 7, 8 und 10.

Wenn der Webserver auf eine Namespace-Deklaration stößt, durchsucht er die [META-INF]-Verzeichnisse im Klassenpfad der Anwendung nach Dateien mit der Endung .tld (TagLib Definition). Hier findet er sie im [jsf-impl.jar]-Archiv [1,2]:

Werfen wir einen Blick auf [3] die Datei [HTML_basic.tld]:

<?xml version="1.0" encoding="UTF-8"?>

<taglib xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd" version="2.1">

<!-- ============== Tag Library Description Elements ============= -->

    <description>
        This tag library contains JavaServer Faces component tags for all
        UIComponent + HTML RenderKit Renderer combinations defined in the
        JavaServer Faces Specification.
    </description>
    <tlib-version>
        2.1
    </tlib-version>
    <short-name>
        h
    </short-name>
    <uri>
        http://java.sun.com/jsf/html
    </uri>

<!-- ============== Tag Library Validator ============= -->
...
  • in Zeile 19 die URI der Tag-Bibliothek,
  • in Zeile 16 ihr Kurzname.

Die Definitionen der verschiedenen <h:xx>-Tags befinden sich in dieser Datei. Diese Tags werden von Java-Klassen verwaltet, die sich ebenfalls im Artefakt [jsf-impl.jar] befinden.

Kehren wir zu unserem JSF-Projekt zurück. Es wurde um einen neuen Zweig erweitert:

Der Zweig [Other Sources] [1] enthält Dateien, die sich im Klassenpfad des Projekts befinden müssen und keinen Java-Code darstellen. Dies gilt für JSF-Meldungsdateien. Wir haben gesehen, dass dieser Zweig fehlt, wenn das JSF-Framework nicht zum Projekt hinzugefügt wurde. Um ihn zu erstellen, legen Sie einfach den Ordner [src/main/resources] [3] auf der Registerkarte [Files] [2] an.

Schließlich ist im Zweig [Web Pages] ein neuer Ordner erschienen:

Der Ordner [WEB-INF] wurde erstellt und enthält die Datei [ web.xml]. Diese Datei konfiguriert die Webanwendung:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>
  • In den Zeilen 7–10 wird ein Servlet definiert, d. h. eine Java-Klasse, die Client-Anfragen verarbeiten kann. Eine JSF-Anwendung funktioniert wie folgt:

Diese Architektur implementiert das MVC-Entwurfsmuster (Model, View, Controller). Lassen Sie uns noch einmal zusammenfassen, was zuvor erwähnt wurde. Die Verarbeitung einer Client-Anfrage umfasst die folgenden vier Schritte:

1 – Anfrage – Der Client-Browser sendet eine Anfrage an den Controller [Faces Servlet]. Der Controller verarbeitet alle Client-Anfragen. Er ist der Einstiegspunkt der Anwendung. Dies ist das C in MVC,

2 – Verarbeitung – der C-Controller verarbeitet diese Anfrage. Dabei wird er von anwendungsspezifischen Ereignisbehandlungsroutinen unterstützt [2a]. Diese Routinen benötigen unter Umständen Unterstützung durch die Geschäftslogik [2b]. Sobald die Client-Anfrage verarbeitet wurde, kann dies verschiedene Antworten auslösen. Ein klassisches Beispiel ist:

  • eine Fehlerseite, wenn die Anfrage nicht korrekt verarbeitet werden konnte;
  • ansonsten eine Bestätigungsseite,

3 – Navigation – Der Controller wählt die Antwort (= Ansicht) aus, die an den Client gesendet werden soll. Die Auswahl der an den Client zu sendenden Antwort umfasst mehrere Schritte:

  • Auswahl des Facelets, das die Antwort generiert. Dies wird als V-Ansicht bezeichnet, das V in MVC. Diese Wahl hängt in der Regel vom Ergebnis der Ausführung der vom Benutzer angeforderten Aktion ab;
  • Versorgung dieses Facelets mit den Daten, die es zur Generierung dieser Antwort benötigt. Tatsächlich enthält diese Antwort meist Informationen, die vom Controller berechnet wurden. Diese Informationen bilden das sogenannte M-Modell der Ansicht, das M in MVC;

Schritt 3 besteht daher darin, eine Ansicht V auszuwählen und das dafür erforderliche Modell M zu erstellen.

4 – Antwort – Der Controller C weist das ausgewählte Facelet an, sich selbst darzustellen. Das Facelet verwendet das vom Controller C vorbereitete Modell M, um die dynamischen Teile der Antwort zu initialisieren, die es an den Client senden muss. Das genaue Format dieser Antwort kann variieren: Es kann sich um einen HTML-Stream, eine PDF-Datei, eine Excel-Datei usw. handeln.

In einem JSF-Projekt:

  • ist der Controller C das Servlet [javax.faces.webapp.FacesServlet],
  • die V-Ansichten werden durch Seiten unter Verwendung der Facelets-Technologie implementiert,
  • M-Modelle und Ereignisbehandler werden durch Java-Klassen implementiert, die oft als „Backing Beans“ oder, einfacher gesagt, als Beans bezeichnet werden.

Werfen wir noch einmal einen Blick auf den Inhalt der Datei [web.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>
  • Zeilen 12–15: Das <servlet-mapping>-Tag wird verwendet, um ein Servlet mit einer vom Client-Browser angeforderten URL zu verknüpfen. Hier wird festgelegt, dass URLs der Form [/faces/*] von dem Servlet namens [Faces Servlet] verarbeitet werden müssen. Dieses Servlet ist in den Zeilen 7–10 definiert. Da es keine weiteren <servlet-mapping>-Tags in der Datei gibt, bedeutet dies, dass das [Faces Servlet] nur URLs der Form [/faces/*] verarbeitet. Wir haben gesehen, dass der Anwendungskontext den Namen [/mv-jsf2-01] trägt. Client-URLs, die vom [Faces Servlet] verarbeitet werden, haben daher die Form [http://machine:port/mv-jsf2-01/faces/*]. .html- und .jsp-Seiten werden standardmäßig vom Servlet-Container selbst verarbeitet, nicht von einem bestimmten Servlet. Dies liegt daran, dass der Servlet-Container weiß, wie er sie verarbeiten muss.
  • Zeilen 7–10: definieren das [Faces-Servlet]. Da alle akzeptierten URLs an dieses weitergeleitet werden, ist es der C-Controller im MVC-Modell,
  • Zeile 10: gibt an, dass das Servlet in den Speicher geladen werden muss, sobald der Webserver startet. Standardmäßig wird ein Servlet erst beim Empfang der ersten an es gerichteten Anfrage geladen,
  • Zeilen 3–6: definieren einen Parameter für das [Faces Servlet]. Der Parameter javax.faces.PROJECT_STAGE definiert die Phase des laufenden Projekts. In der Entwicklungsphase zeigt das [Faces Servlet] Fehlermeldungen an, die für die Fehlersuche nützlich sind. In der Produktionsphase werden diese Meldungen nicht mehr angezeigt,
  • Zeilen 17–19: Sitzungsdauer in Minuten. Ein Client interagiert mit der Anwendung über eine Reihe von Anfrage-/Antwortzyklen. Jeder Zyklus verwendet eine eigene TCP/IP-Verbindung, die bei jedem Zyklus neu aufgebaut wird. Wenn also ein Client C zwei Anfragen, D1 und D2, stellt, hat der Server S keine Möglichkeit zu erkennen, dass die beiden Anfragen zum selben Client C gehören. Der Server S verfügt nicht über den Client- -Speicher. Dies ist ein Merkmal des HTTP-Protokolls (HyperText Transfer Protocol): Der Client kommuniziert mit dem Server über eine Reihe von Client-Anfrage-/Server-Antwort-Zyklen, wobei jeder eine neue TCP/IP-Verbindung nutzt. Dies wird als zustandsloses Protokoll bezeichnet. Bei anderen Protokollen, wie beispielsweise FTP (File Transfer Protocol), nutzt der Client C für die Dauer seiner Interaktion mit dem Server S dieselbe Verbindung. Eine Verbindung ist somit an einen bestimmten Client gebunden. Der Server S weiß somit stets, mit wem er es zu tun hat. Um zu erkennen, dass eine Anfrage zu einem bestimmten Client gehört, kann der Webserver die Session-Technik nutzen:
    • Wenn ein Client seine erste Anfrage stellt, sendet Server S die erwartete Antwort sowie ein Token, eine zufällige Zeichenfolge, die für diesen Client einzigartig ist;
    • Bei jeder nachfolgenden Anfrage sendet Client C das empfangene Token an Server S zurück, wodurch Server S ihn erkennen kann.

Die Anwendung kann nun den Server auffordern, Informationen zu speichern, die mit einem bestimmten Client verbunden sind. Dies wird als Client-Sitzung bezeichnet. Zeile 18 gibt an, dass die Lebensdauer einer Sitzung 30 Minuten beträgt. Das bedeutet, dass die Sitzung beendet wird und die darin enthaltenen Informationen verloren gehen, wenn ein Client C innerhalb von 30 Minuten keine neue Anfrage stellt. Bei der nächsten Anfrage verläuft alles so, als wäre er ein neuer Client, und eine neue Sitzung beginnt;

  • Zeilen 21–23: Die Liste der Seiten, die angezeigt werden sollen, wenn der Benutzer den Kontext anfordert, ohne eine Seite anzugeben, zum Beispiel hier [http://machine:port/mv-jsf2-01]. In diesem Fall prüft der Webserver (nicht das Servlet), ob die Anwendung ein <welcome-file-list>-Tag definiert hat. Ist dies der Fall, wird die erste in der Liste gefundene Seite angezeigt. Existiert diese Seite nicht, wird die zweite Seite angezeigt und so weiter, bis eine vorhandene Seite gefunden wird. Wenn der Client hier die URL [http://machine:port/mv-jsf2-01] anfordert, wird die URL [http://machine:port/mv-jsf2-01/index.xhtml] bereitgestellt.

2.3.5. Führen Sie das Projekt aus

Wenn das neue Projekt ausgeführt wird, wird im Browser folgendes Ergebnis angezeigt:

  • In [1] wurde der Kontext ohne Angabe eines Dokuments angefordert,
  • in [2] wird, wie erläutert, die Startseite (welcome-file) [index.xhtml] bereitgestellt.

Vielleicht möchten Sie sich den empfangenen Quellcode [3] einmal ansehen:

1
2
3
4
5
6
7
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
    <title>Facelet Title</title></head><body>
    Hello from Facelets
  </body>
</html>

Wir haben HTML-Code erhalten. Alle <h:xx>-Tags in index.xhtml wurden in die entsprechenden HTML-Tags übersetzt.

2.3.6. Das lokale Maven-Repository

Wir haben erwähnt, dass Maven die für das Projekt erforderlichen Abhängigkeiten herunterlädt und lokal speichert. Sie können dieses lokale Repository erkunden:

  • Wählen Sie unter [1] die Option [Fenster / Sonstiges / Maven-Repository-Browser] aus.
  • in [2] öffnet sich die Registerkarte [Maven-Repositorys],
  • in [3] enthält dieser zwei Zweige, einen für das lokale Repository und einen für das zentrale Repository. Letzteres ist riesig. Um dessen Inhalt anzuzeigen, müssen Sie den Index aktualisieren [4]. Diese Aktualisierung dauert mehrere Dutzend Minuten.
  • In [5] finden Sie die Bibliotheken im lokalen Repository,
  • in [6] finden Sie einen Zweig [istia.st], der der [groupId] unseres Projekts entspricht,
  • in [7] können Sie auf die Eigenschaften des lokalen Repositorys zugreifen,
  • in [8] sehen Sie den Pfad zum lokalen Repository. Es ist nützlich, dies zu wissen, da Maven manchmal (selten) nicht mehr die neueste Version des Projekts verwendet. Sie nehmen Änderungen vor und stellen fest, dass diese nicht übernommen werden. Sie können dann den Zweig im lokalen Repository, der Ihrer [groupId] entspricht, manuell löschen. Dies zwingt Maven dazu, den Zweig aus der neuesten Version des Projekts neu zu erstellen.

2.3.7. Suche nach einem Artefakt mit Maven

Lassen Sie uns nun lernen, wie man mit Maven nach einem Artefakt sucht. Beginnen wir mit der Liste der aktuellen Abhängigkeiten in der [pom.xml]-Datei:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>istia.st</groupId>
  <artifactId>mv-jsf2-01</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
 
  <name>mv-jsf2-01</name>
 
  ...
  <dependencies>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.1-b04</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    ...
  </build>
  <repositories>
    <repository>
      <url>http://download.java.net/maven/2/</url>
      <id>jsf20</id>
      <layout>default</layout>
      <name>Repository for library Library[jsf20]</name>
    </repository>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>jstl11</id>
      <layout>default</layout>
      <name>Repository for library Library[jstl11]</name>
    </repository>
  </repositories>
</project>

Die Zeilen 13–40 definieren Abhängigkeiten, und die Zeilen 45–58 geben die Repositorys an, in denen diese zu finden sind, zusätzlich zum zentralen Repository, das immer verwendet wird. Wir werden die Abhängigkeiten so anpassen, dass die Bibliotheken in ihren neuesten Versionen verwendet werden.

Zunächst entfernen wir die aktuellen Abhängigkeiten [1]. Die Datei [pom.xml] wird dann wie folgt geändert:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
...
    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
...
    <repositories>
        <repository>
            <url>http://download.java.net/maven/2/</url>
            <id>jsf20</id>
            <layout>default</layout>
            <name>Repository for library Library[jsf20]</name>
        </repository>
        <repository>
            <url>http://repo1.maven.org/maven2/</url>
            <id>jstl11</id>
            <layout>default</layout>
            <name>Repository for library Library[jstl11]</name>
        </repository>
    </repositories>
</project>

Zeilen 5–12: Die entfernten Abhängigkeiten erscheinen nicht mehr in [pom.xml]. Suchen wir nun in den Maven-Repositorys danach.

  • In [1] fügen wir dem Projekt eine Abhängigkeit hinzu;
  • in [2] müssen wir Informationen zu dem gesuchten Artefakt angeben (groupId, artifactId, version, packaging (Type) und scope). Wir beginnen mit der Angabe der [groupId] [3],
  • in [4] drücken wir die [Leertaste], um die Liste der möglichen Artefakte anzuzeigen. Hier sind [jsf-api] und [jsf-impl]. Wir wählen [jsf-api],
  • in [5] wählen wir nach dem gleichen Verfahren die aktuellste Version aus. Der Packaging-Typ ist jar.

Wir verfahren auf diese Weise für alle Artefakte:

Unter [6] werden die hinzugefügten Abhängigkeiten im Projekt angezeigt. Die Datei [pom.xml] spiegelt diese Änderungen wider:


<dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Nehmen wir nun an, wir kennen die [groupId] des gewünschten Artefakts nicht. Wir möchten beispielsweise Hibernate als ORM (Object Relational Mapper) verwenden, und das ist alles, was wir wissen. Dann können wir die Website [http://mvnrepository.com/] aufrufen:

Unter [1] können Sie Stichwörter eingeben. Geben Sie „hibernate“ ein und starten Sie die Suche.

  • Wählen Sie unter [2] die [groupId] „org.hibernate“ und die [artifactId] „hibernate-core“ aus.
  • Wählen Sie in [3] die Version 4.1.2-Final aus.
  • in [4] erhalten wir den Maven-Code, den wir in die [pom.xml]-Datei einfügen müssen. Machen wir das.

<dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.1.2.Final</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.7</version>
      <type>jar</type>
    </dependency>
    ...
  </dependencies>

Wir speichern die Datei [pom.xml]. Maven lädt daraufhin die neuen Abhängigkeiten herunter. Das Projekt entwickelt sich wie folgt:

  • in [5] die Abhängigkeit [hibernate-core-4.1.2-Final]. In dem Repository, in dem sie gefunden wurde, wird diese [artifactId] ebenfalls durch eine [pom.xml]-Datei beschrieben. Diese Datei wurde gelesen, und Maven stellte fest, dass die [artifactId] Abhängigkeiten hatte. Diese lädt es ebenfalls herunter. Dies geschieht für jede heruntergeladene [artifactId]. Letztendlich finden wir in [6] Abhängigkeiten, die wir nicht direkt angefordert haben. Sie sind durch ein anderes Symbol gekennzeichnet als das des Haupt-[artifactId].

In diesem Dokument nutzen wir Maven in erster Linie für diese Funktion. Das erspart uns, alle Abhängigkeiten einer Bibliothek kennen zu müssen, die wir verwenden wollen. Wir lassen Maven diese verwalten. Darüber hinaus stellen wir durch die gemeinsame Nutzung einer [pom.xml]-Datei unter den Entwicklern sicher, dass jeder Entwickler tatsächlich dieselben Bibliotheken verwendet.

In den folgenden Beispielen stellen wir lediglich die verwendete [pom.xml]-Datei zur Verfügung. Der Leser muss sie lediglich verwenden, um die im Dokument beschriebenen Bedingungen nachzubilden. Darüber hinaus werden Maven-Projekte von den gängigen Java-IDEs (Eclipse, NetBeans, IntelliJ, JDeveloper) unterstützt. Somit kann der Leser seine bevorzugte IDE verwenden, um die Beispiele zu testen.

2.4. Beispiel mv-jsf2-02: Ereignisbehandler – Internationalisierung – Seitennavigation

2.4.1. Die Anwendung

Die Anwendung sieht wie folgt aus:

  • [1], die Startseite der Anwendung,
  • in [2] zwei Links zum Ändern der Sprache der Seiten der Anwendung,
  • unter [3] einen Navigationslink zu einer anderen Seite,
  • wenn Sie auf [3] klicken, wird Seite [4] angezeigt,
  • über den Link [5] gelangen Sie zurück zur Startseite.
  • Auf der Startseite [1] können Sie über die Links [2] die Sprache ändern,
  • in [3] die Startseite auf Englisch.

2.4.2. Das NetBeans-Projekt

Wir erstellen ein neues Webprojekt, wie in Abschnitt 2.3.1 beschrieben. Wir nennen es mv-jsf2-02:

  • In [1], dem generierten Projekt,
  • in [2] haben wir das Paket [istia.st.mvjsf202] und die Datei [index.jsp] entfernt,
  • in [3] haben wir Maven-Abhängigkeiten mithilfe der folgenden [pom.xml]-Datei hinzugefügt:

<dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

Die hinzugefügten Abhängigkeiten stammen aus dem JSF-Framework. Kopieren Sie einfach die obigen Zeilen in die Datei [pom.xml], um die alten Abhängigkeiten zu ersetzen.

  • In [4, 5]: Erstellen Sie im Reiter [Dateien] einen Ordner [src/main/resources],
  • in [6] wurde auf der Registerkarte [Projekte] der Zweig [Andere Quellen] erstellt.

Wir haben nun ein JSF-Projekt. Wir werden darin verschiedene Dateitypen erstellen:

  • Webseiten im XHTML-Format,
  • Java-Klassen,
  • Meldungsdateien,
  • die JSF-Projektkonfigurationsdatei.

Schauen wir uns an, wie man die einzelnen Dateitypen erstellt:

  • In [1] erstellen wir eine JSF-Seite
  • In [2] erstellen wir eine [index.xhtml]-Seite im [Facelets]-Format [3],
  • in [4] wurden zwei Dateien erstellt: [index.xhtml] und [WEB-INF/web.xml].

Die Datei [ web.xml] konfiguriert die JSF-Anwendung. Sie sieht wie folgt aus:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <URL-pattern>/faces/*</URL-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    <welcome-file-list>
        <welcome-file>faces/index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

Wir haben diese Datei bereits in Abschnitt 2.3.4 behandelt. Sehen wir uns noch einmal ihre wichtigsten Eigenschaften an:

  • Alle URLs vom Typ faces/* werden vom Servlet [javax.faces.webapp.FacesServlet] verarbeitet,
  • die Seite [index.xhtml] ist die Startseite der Anwendung.

Die erstellte Datei [index.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

Wir sind dieser Datei bereits in Abschnitt 2.3.4 begegnet.

Erstellen wir nun eine Java-Klasse:

  • Erstellen Sie in [1] im Zweig [Source Packages] eine Java-Klasse,
  • in [2] geben wir ihr einen Namen und ordnen sie einem Paket zu [3],
  • in [4] erscheint die erstellte Klasse im Projekt.

Der Code für die erstellte Klasse ist ein Klassenskelett:


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package istia.st;
 
/**
 *
 * @author Serge Tahé
 */
public class Form {
  
}

Zuletzt erstellen wir noch eine Meldungsdatei:

  • Erstellen Sie in [1] eine [Properties]-Datei,
  • Geben Sie in [2] den Dateinamen und in [3] den Ordner ein.
  • in [4] wurde die Datei [messages.properties] erstellt.

Manchmal ist es notwendig, die Datei [WEB-INF/faces-config.xml] zu erstellen, um das JSF-Projekt zu konfigurieren. Diese Datei war bei JSF 1 erforderlich. Bei JSF 2 ist sie optional. Sie ist jedoch notwendig, wenn die JSF-Website internationalisiert ist. Dies wird später der Fall sein. Daher zeigen wir Ihnen nun, wie Sie diese Konfigurationsdatei erstellen.

  • In [1] erstellen wir die JSF-Konfigurationsdatei,
  • in [2] geben wir ihren Namen ein und in [3] ihren Ordner,
  • in [4] die erstellte Datei.

Die erstellte Datei [faces-config.xml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
</faces-config>

Das Stamm-Tag lautet <faces-config>. Der Inhalt dieses Tags ist leer. Wir müssen ihn später ausfüllen.

Wir verfügen nun über alle Elemente, die zum Erstellen eines JSF-Projekts erforderlich sind. In den folgenden Beispielen stellen wir das vollständige JSF-Projekt vor und erläutern anschließend seine Elemente nacheinander. Wir stellen nun ein Projekt vor, um die Konzepte zu erläutern:

  • Formularereignisbehandlung,
  • Internationalisierung von Seiten auf einer JSF-Website,
  • Navigation zwischen Seiten.

Das Projekt [mv-jsf2-02] sieht wie folgt aus. Leser finden es auf der Beispiel-Website (siehe Abschnitt 1.2).

  • in [1] die JSF-Projektkonfigurationsdateien,
  • in [2] die JSF-Seiten des Projekts,
  • in [3] die einzelne Java-Klasse,
  • in [4] die Meldungsdateien.

2.4.3. Die Seite [index.xhtml]

Die Datei [index.xhtml] [1] sendet die Seite [2] an den Browser des Clients:

Der Code, der diese Seite generiert, lautet wie folgt:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      ...
    </head>
    <body>
      ....
    </body>
  </f:view>
</html>
  • Zeilen 7–9: Die von der Seite verwendeten Namespaces/Tag-Bibliotheken. Tags mit dem Präfix „h“ sind HTML-Tags, während Tags mit dem Präfix „f“ JSF-spezifische Tags sind,
  • Zeile 10: Das <f:view>-Tag dient dazu, den Code abzugrenzen, den die JSF-Engine verarbeiten muss, d. h. den Bereich, in dem die <f:xx>-Tags vorkommen. Mit dem Attribut „locale“ können Sie eine Anzeigesprache für die Seite festlegen. Hier verwenden wir zwei: Englisch und Französisch. Der Wert des Attributs „locale“ wird als EL-Ausdruck (Expression Language) #{Ausdruck} angegeben. Die Form des Ausdrucks kann variieren. Meistens drücken wir ihn als bean['key'] oder bean.field aus. In unseren Beispielen ist bean entweder eine Java-Klasse oder eine Nachrichtendatei. Bei JSF 1 mussten diese Beans in der Datei [faces-config.xml] deklariert werden. Bei JSF 2 ist dies für Java-Klassen nicht mehr zwingend erforderlich. Wir können nun Annotationen verwenden, die eine Java-Klasse in eine von JSF 2 erkannte Bean umwandeln. Die Message-Datei muss in der Konfigurationsdatei [faces-config.xml] deklariert werden.

2.4.4. Die [ changeLocale]-Bean

Im EL-Ausdruck „#{changeLocale.locale}“:

  • ist changeLocale der Name einer Bean, in diesem Fall der Java-Klasse ChangeLocale,
  • locale ist ein Feld der Klasse ChangeLocale. Der Ausdruck wird als [ChangeLocale].getLocale() ausgewertet. Im Allgemeinen wird der Ausdruck #{bean.field} als [Bean].getField() ausgewertet, wobei [Bean] eine Instanz der Java-Klasse ist, der der Name bean zugewiesen wurde, und getField der Getter ist, der dem Feld der Bean zugeordnet ist.

Die Klasse ChangeLocale sieht wie folgt aus:


package utils;
 
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;
 
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // page locale
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  ...
  public String getLocale() {
    return locale;
  }
  
}
  • Zeile 11: das Feld „locale“,
  • Zeile 17: sein Getter,
  • Zeile 7: Die Annotation „ManagedBean“ macht die Java-Klasse „ChangeLocale“ zu einem von JSF erkannten Bean. Ein Bean wird durch einen Namen identifiziert. Dieser kann über das Attribut „name“ der Annotation festgelegt werden: @ManagedBean(name="xx"). Wird das Attribut „name“ weggelassen, wird der Klassenname verwendet, wobei das erste Zeichen kleingeschrieben wird. Der Name des ChangeLocale-Beans lautet daher changeLocale. Beachten Sie, dass die ManagedBean-Annotation zum Paket javax.faces.bean.ManagedBean gehört und nicht zum Paket javax.annotations.ManagedBean.
  • Zeile 8: Die Annotation „SessionScoped“ definiert den Gültigkeitsbereich des Beans. Es gibt mehrere Gültigkeitsbereiche. Wir werden üblicherweise die folgenden drei verwenden:
    • RequestScoped: Die Lebensdauer der Bean entspricht dem Zyklus von Browseranfrage und Serverantwort. Wenn diese Bean erneut benötigt wird, um eine neue Anfrage desselben oder eines anderen Browsers zu verarbeiten, wird sie erneut instanziiert.
    • SessionScoped: Die Lebensdauer der Bean entspricht der einer bestimmten Client-Sitzung. Die Bean wird zunächst erstellt, um eine der Anfragen dieses Clients zu bearbeiten. Sie verbleibt dann im Speicher innerhalb der Sitzung dieses Clients. Eine solche Bean speichert typischerweise Daten, die für einen bestimmten Client spezifisch sind. Sie wird zerstört, wenn die Sitzung des Clients beendet wird.
    • ApplicationScoped: Die Lebensdauer der Bean entspricht der der Anwendung selbst. Eine Bean mit diesem Gültigkeitsbereich wird meist von allen Clients der Anwendung gemeinsam genutzt. Sie wird in der Regel beim Start der Anwendung initialisiert.

Diese Annotationen sind in zwei Paketen vorhanden: javax.enterprise.context.SessionScoped (JSF 2) und javax.faces.bean.SessionScoped (JSF 1). Hier verwenden wir das JSF-2-Paket. Dazu müssen wir die Datei [WEB-INF/beans.xml] erstellen:

  

Diese Datei wird von NetBeans automatisch generiert, wenn Sie das Paket [javax.enterprise.context.SessionScoped] importieren. Der Inhalt lautet wie folgt:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

Außerhalb des Stamm-Tags <beans> ist die Datei leer. Das reicht aus. Es ist lediglich erforderlich, dass sie vorhanden ist.

Beachten Sie abschließend, dass die Klasse [ChangeLocale] die Schnittstelle [Serializable] implementiert. Dies ist für Beans im Session-Bereich erforderlich, die der Webserver möglicherweise in Dateien serialisieren muss. Wir werden später auf die Bean [ChangeLocale] zurückkommen.

2.4.5. Die Meldungsdatei

Kehren wir zur Datei [index.xhtml] zurück:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
    ...
    </body>
  </f:view>
</html>
  • Zeile 8: Das <h:outputText>-Tag zeigt den Wert eines EL-Ausdrucks #{msg['welcome.title']} in der Form #{bean['field']} an. bean ist entweder der Name einer Java-Klasse oder der einer Nachrichtendatei. Hier ist es der Name einer Nachrichtendatei. Die Nachrichtendatei muss in der Konfigurationsdatei [faces-config.xml] deklariert sein. Die msg-Bean ist wie folgt deklariert:

<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>
  • Zeilen 11–18: Das <application>-Tag dient zur Konfiguration der JSF-Anwendung,
  • Zeilen 12–17: Das <resource-bundle>-Tag dient zur Definition von Ressourcen für die Anwendung, in diesem Fall einer Meldungsdatei,
  • Zeilen 13–15: Das <base-name>-Tag definiert den Namen der Meldungsdatei,
  • Zeile 14: Die Datei erhält den Namen messages[_LanguageCode][_CountryCode].properties“. Das <base-name>-Tag definiert nur den ersten Teil des Namens. Der Rest wird impliziert. Es kann mehrere Meldungsdateien geben, eine pro Sprache:
  • In [1] sehen wir vier Meldungsdateien, die dem in [faces-config.xml] definierten Basisnamen `messages` entsprechen:
    • messages_fr.properties: enthält Meldungen auf Französisch (Code fr);
    • messages_en.properties: enthält Meldungen auf Englisch (Code en);
    • messages_es_ES.properties: enthält Meldungen auf Spanisch (Code es) aus Spanien (Code ES). Es gibt weitere Varianten des Spanischen, wie beispielsweise das Spanisch aus Bolivien (es_BO);
    • messages.properties: wird vom Server verwendet, wenn für die Sprache des Rechners, auf dem er läuft, keine zugehörige Meldungsdatei vorhanden ist. Dies wäre beispielsweise der Fall, wenn die Anwendung auf einem Rechner in Deutschland laufen würde, wo die Standardsprache Deutsch (de) ist. Da es keine Datei [messages_de.properties] gibt, würde die Anwendung die Datei [messages.properties] verwenden,
  • in [2]: Sprachcodes unterliegen einem internationalen Standard,
  • in [3]: Das Gleiche gilt für Ländercodes.

Der Name der Meldungsdatei wird in Zeile 14 definiert. Nach ihr wird im Klassenpfad des Projekts gesucht. Befindet sie sich in einem Paket, muss dieses Paket in Zeile 14 definiert sein, zum Beispiel resources.messages, wenn sich die Datei [messages.properties] im Ordner [resources] des Klassenpfads befindet. Da der Name in Zeile 14 kein Paket enthält, muss die Datei [messages.properties] im Stammverzeichnis des Ordners [src/main/resources] abgelegt werden:

In [1] wird auf der Registerkarte [Projects] des NetBeans-Projekts die Datei [messages.properties] als Liste der verschiedenen definierten Nachrichtenversionen angezeigt. Die Versionen werden durch eine Folge von ein bis drei Codes [languageCode_countryCode_variantCode] gekennzeichnet. In [1] wurde nur der [Sprachcode] verwendet: en für Englisch, fr für Französisch. Jede Version wird in einer separaten Datei im Dateisystem gespeichert.

In unserem Beispiel enthält die französische Meldungsdatei [messages_fr.properties] Folgendes:


welcome.titre=Tutoriel JSF (JavaServer Faces)
welcome.langue1=Fran\u00e7ais
welcome.langue2=Anglais
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Page d'accueil

Die Datei [messages_en.properties] sieht wie folgt aus:


welcome.titre=JSF (JavaServer Faces) Tutorial
welcome.langue1=French
welcome.langue2=English
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Welcome page

Die Datei [messages.properties] ist identisch mit der Datei [messages_en.properties]. Letztendlich hat der Browser des Kunden die Wahl zwischen Seiten auf Französisch und Seiten auf Englisch.

Kehren wir zur Datei [faces-config.xml] zurück, in der die Nachrichten-Datei deklariert wird:


...
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>

Zeile 8 gibt an, dass eine Zeile in der Nachrichtendatei in den JSF-Seiten über den Bezeichner msg referenziert wird. Dieser Bezeichner wird in der besprochenen Datei [index.xhtml] verwendet:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      ...
    </body>
  </f:view>
</html>

Das <h:outputText>-Tag in Zeile 8 zeigt den Wert der Meldung (Vorhandensein des Bezeichners „msg“) mit dem Schlüssel „welcome.title“ an. Diese Meldung wird in der Datei [messages.properties] für die aktuell aktive Sprache gesucht und gefunden. Beispiel für Französisch:


welcome.titre=Tutoriel JSF (JavaServer Faces)

Eine Meldung folgt dem Format Schlüssel=Wert. Zeile 8 der Datei [index.xhtml] sieht nach Auswertung des Ausdrucks #{msg['welcome.title']} wie folgt aus:


      <title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>

Dieser Mechanismus der Nachrichtendateien erleichtert die Änderung der Sprache der Seiten in einem JSF-Projekt. Dies wird als Projekt-Internationalisierung bezeichnet, oder häufiger mit der Abkürzung i18n, da das Wort Internationalisierung mit i beginnt und mit n endet und sich zwischen dem i und dem n 18 Buchstaben befinden.

2.4.6. Das Formular

Schauen wir uns den Inhalt der Datei [index.xhtml] weiter an:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>
  • Zeilen 11–18: Das <h:form>-Tag leitet ein Formular ein. Ein Formular besteht im Allgemeinen aus:
    • Eingabefeld-Tags (Text, Optionsfelder, Kontrollkästchen, Dropdown-Listen usw.);
    • Tags zur Formularvalidierung (Schaltflächen, Links). Über eine Schaltfläche oder einen Link übermittelt der Benutzer seine Eingaben an den Server, der diese verarbeitet.

Jedes JSF-Tag kann durch ein id-Attribut identifiziert werden. Meistens ist dies nicht erforderlich, was auch für die meisten der hier verwendeten JSF-Tags gilt. In bestimmten Situationen ist dieses Attribut jedoch nützlich. In Zeile 17 wird das Formular durch die ID „form“ identifiziert. In diesem Beispiel wird die ID des Formulars nicht verwendet und hätte weggelassen werden können.

  • Zeilen 18–21: Das `<h:panelGrid>`-Tag definiert hier eine zweispaltige HTML-Tabelle. Es generiert das HTML-Tag `<table>`.
  • Das Formular verfügt über drei Links, die seine Verarbeitung auslösen, in den Zeilen 19, 20 und 23. Das <h:commandLink>-Tag hat mindestens zwei Attribute:
    • value: der Text des Links;
    • action: entweder eine C-Zeichenkette oder die Referenz auf eine Methode, die bei Ausführung die C-Zeichenkette zurückgibt. Diese C-Zeichenkette kann entweder
      • entweder der Name einer JSF-Seite im Projekt
      • oder ein Name, der in den Navigationsregeln der Datei [faces-config.xml] definiert und einer JSF-Seite im Projekt zugeordnet ist;

In beiden Fällen wird die JSF-Seite angezeigt, sobald die durch das „action“-Attribut definierte Aktion ausgeführt wurde.

Betrachten wir die Funktionsweise der Formularverarbeitung am Beispiel des Links in Zeile 13:


         <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>}"/>

Zunächst wird die Nachrichtendatei verwendet, um den Ausdruck #{msg['welcome.langue1']} durch seinen Wert zu ersetzen. Nach der Auswertung lautet das Tag:


<h:commandLink value="Français" action="#{changeLocale.setFrenchLocale}"/>}"/>

Die HTML-Übersetzung dieses JSF-Tags sieht wie folgt aus:


<a href="#" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),{'formulaire:j_idt8':'formulaire:j_idt8'},'');return false">Français</a>

was zu folgendem visuellen Erscheinungsbild führt:

Beachten Sie das onclick-Attribut des HTML-Tags <a>. Wenn der Benutzer auf den Link [Französisch] klickt, wird JavaScript-Code ausgeführt. Dieser Code ist in die vom Browser empfangene Seite eingebettet, und es ist der Browser, der ihn ausführt. JavaScript wird häufig in JSF und AJAX (Asynchronous JavaScript and XML) verwendet. Sein allgemeiner Zweck besteht darin, die Benutzerfreundlichkeit und Reaktionsfähigkeit von Webanwendungen zu verbessern. Meistens wird es automatisch von Software-Tools generiert, sodass es nicht notwendig ist, es zu verstehen. Manchmal muss ein Entwickler jedoch JavaScript-Code zu seinen JSF-Seiten hinzufügen. In solchen Fällen sind Kenntnisse in JavaScript erforderlich.

Es ist hier nicht notwendig, den für das JSF-Tag <h:commandLink> generierten JavaScript-Code zu verstehen. Zwei Punkte sind jedoch erwähnenswert:

  • Der JavaScript-Code verwendet die Formular-ID, die wir dem JSF-Tag <h:form> zugewiesen haben.
  • JSF generiert automatische Bezeichner für alle Tags, bei denen das id-Attribut nicht definiert wurde. Hier ein Beispiel: j_idt8. Die Vergabe eindeutiger Bezeichner für Tags erleichtert bei Bedarf das Verständnis des generierten JavaScript-Codes. Dies gilt insbesondere dann, wenn der Entwickler selbst JavaScript-Code hinzufügen muss, um die Komponenten der Seite zu manipulieren. In diesem Fall muss er die IDs seiner Komponenten kennen.

Was passiert, wenn der Benutzer auf den Link [Französisch] auf der obigen Seite klickt? Betrachten wir die Architektur einer JSF-Anwendung:

Der [Faces Servlet]-Controller empfängt die Anfrage vom Client-Browser im folgenden HTTP-Format:

1
2
3
4
5
6
POST /mv-jsf2-02/faces/index.xhtml HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-URLencoded
Content-Length: 126

formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 
  • Zeilen 1–2: Der Browser fordert die URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml] an. Dies ist immer der Fall: Eingaben, die in einem JSF-Formular vorgenommen wurden, das ursprünglich über die URL „formulaire“ abgerufen wurde, werden an dieselbe URL gesendet. Der Browser hat zwei Möglichkeiten, die eingegebenen Werte zu senden: GET und POST. Bei der GET-Methode werden die eingegebenen Werte vom Browser in der angeforderten URL gesendet. Im obigen Beispiel hätte der Browser die folgende erste Zeile senden können:

GET /mv-jsf2-02/faces/index.xhtml?formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 HTTP/1.1

Unter Verwendung der POST-Methode sendet der Browser hier die eingegebenen Werte über Zeile 6 an den Server.

  • Zeile 3: gibt das Kodierungsformat der Formularwerte an,
  • Zeile 4: gibt die Größe in Bytes von Zeile 6 an,
  • Zeile 5: eine Leerzeile, die das Ende der HTTP-Header und den Beginn der 126 Bytes an Formularwerten anzeigt,
  • Zeile 6: die Formularwerte im Format element1=value1&element2=value2& ..., wobei das Kodierungsformat durch Zeile 3 definiert ist. In diesem Kodierungsformat werden bestimmte Zeichen durch ihre Hexadezimalwerte ersetzt. Dies ist beim letzten Element der Fall:

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_idt8=formulaire%3Aj_idt8

wobei %3A für das Doppelpunktzeichen steht. Somit wird die Zeichenfolge form:j_idt8=form:j_idt8 an den Server gesendet. Sie erinnern sich vielleicht, dass wir dem Bezeichner j_idt8 bereits begegnet sind, als wir den für das Tag generierten HTML-Code untersucht haben


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

. Er war automatisch von JSF generiert worden. Entscheidend ist hier, dass das Vorhandensein dieses Bezeichners in der vom Client-Browser gesendeten Wertzeichenfolge JSF mitteilt, dass der Link [French] angeklickt wurde. JSF verwendet dann das oben genannte action-Attribut, um zu bestimmen, wie die empfangene Zeichenfolge verarbeitet werden soll. Das Attribut action="#{changeLocale.setFrenchLocale}" teilt JSF mit, dass die Client-Anfrage von der Methode [setFrenchLocale] eines Objekts namens changeLocale bearbeitet werden muss. Erinnern Sie sich daran, dass diese Bean durch Annotationen in der Java-Klasse [ChangeLocale] definiert wurde:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

Der Name eines Beans wird durch das Attribut „name“ der Annotation @ManagedBean definiert. Fehlt dieses Attribut, wird der Klassenname als Bean-Name verwendet, wobei das erste Zeichen in Kleinbuchstaben umgewandelt wird.

Kehren wir zur Browseranfrage zurück:

und zu dem <h:commandLink>-Tag, das den [Französisch]-Link generiert hat, auf den wir geklickt haben:


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

Der Controller leitet die Browseranfrage an den Ereignis-Handler weiter, der durch das action-Attribut des <h:commandLink>-Tags definiert ist. Der Ereignis-Handler M, auf den das action-Attribut eines <h:commandLink>-Befehls verweist, muss die folgende Signatur aufweisen:

public String M();
  • Er erhält keine Parameter. Wir werden sehen, dass er dennoch auf die Client-Anfrage zugreifen kann;
  • und sie muss ein Ergebnis C vom Typ String zurückgeben. Dieser String C kann entweder
    • entweder der Name einer JSF-Seite im Projekt;
    • entweder einen Namen, der in den Navigationsregeln der Datei [faces-config.xml] definiert und einer JSF-Seite im Projekt zugeordnet ist;
    • oder ein Null-Zeiger, wenn der Client-Browser keine Seiten wechseln soll,

In der oben dargestellten JSF-Architektur verwendet der [Faces Servlet]-Controller die vom Ereignis-Handler zurückgegebene Zeichenkette C und, falls erforderlich, seine Konfigurationsdatei [faces-config.xml], um zu bestimmen, welche JSF-Seite er als Antwort an den Client senden soll [4].

Im Tag


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

ist der Ereignisbehandler für den Klick auf den Link [Französisch] die Methode [changeLocale.setFrenchLocale], wobei changeLocale eine Instanz der bereits besprochenen Klasse [ utils.ChangeLocale] ist:


package utils;
 
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
 
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // page locale
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  public String setFrenchLocale(){
    locale="fr";
    return null;
  }
  
  public String setEnglishLocale(){
    locale="en";
    return null;
  }
 
  public String getLocale() {
    return locale;
  }
}

Die Methode setFrenchLocale hat tatsächlich die Signatur von Ereignisbehandlungsroutinen. Denken Sie daran, dass die Ereignisbehandlungsroutine die Client-Anfrage verarbeiten muss. Da sie keine Parameter erhält, wie kann sie dann auf die Anfrage zugreifen? Dafür gibt es mehrere Möglichkeiten:

  • Die Bean B, die den Ereignis-Handler für die JSF-Seite P enthält, ist oft auch diejenige, die das Modell M für diese Seite enthält. Das bedeutet, dass Bean B Felder enthält, die mit den auf Seite P eingegebenen Werten initialisiert werden. Dies erfolgt durch den [Faces Servlet]-Controller, bevor der Event-Handler von Bean B aufgerufen wird. Dieser Handler hat daher über die Felder von Bean B, zu dem er gehört, Zugriff auf die vom Client im Formular eingegebenen Werte und kann diese verarbeiten.
  • Die statische Methode [FacesContext.getCurrentInstance()] vom Typ [FacesContext] ermöglicht den Zugriff auf den Ausführungskontext der aktuellen JSF-Anfrage, bei dem es sich um ein Objekt vom Typ [FacesContext] handelt. Der auf diese Weise erhaltene Ausführungskontext der Anfrage ermöglicht den Zugriff auf die Parameter, die vom Browser des Clients an den Server gesendet wurden, und zwar mithilfe der folgenden Methode:
Map FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()

Wenn die vom Client-Browser gesendeten (POST) Parameter wie folgt lauten:

formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_id_id21=formulaire%3Aj_id_id21

gibt die Methode getRequestParameterMap() das folgende Wörterbuch zurück:

Schlüssel
Wert
Formular
Form
javax.faces.ViewState
...
Formular:j_id_id21
Formular:j_id_id21

Im Tag


          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>

Was wird vom Ereignis-Handler locale.setFrenchLocale erwartet? Wir möchten, dass er die von der Anwendung verwendete Sprache festlegt. In der Java-Fachsprache wird dies als „Lokalisierung“ der Anwendung bezeichnet. Diese Lokalisierung wird vom <f:view>-Tag auf der JSF-Seite [index.xhtml] verwendet:


  <f:view locale="#{changeLocale.locale}">
    ...
</f:view>

Um die Seite auf Französisch umzustellen, setzen Sie das Attribut „locale“ einfach auf „fr“. Um sie auf Englisch umzustellen, setzen Sie es auf „en“. Der Wert des Attributs „locale“ wird mithilfe des Ausdrucks [ChangeLocale].getLocale() ermittelt. Dieser Ausdruck gibt den Wert des Feldes „locale“ in der Klasse [ChangeLocale] zurück. Daraus leiten wir den Code für die Methode [ChangeLocale].setFrenchLocale() ab, die die Seiten auf Französisch umstellen soll:


  public String setFrenchLocale(){
    locale="fr";
    return null;
}

Wir haben erklärt, dass ein Ereignis-Handler eine Zeichenkette im C-Stil zurückgeben muss, die von [Faces Servlet] verwendet wird, um die JSF-Seite zu finden, die als Antwort an den Client-Browser gesendet werden soll. Wenn die zurückzugebende Seite mit der aktuell verarbeiteten übereinstimmt, kann der Ereignis-Handler einfach den Wert null zurückgeben. Genau das geschieht hier in Zeile 3: Wir möchten dieselbe Seite [index.xhtml] zurückgeben, jedoch in einer anderen Sprache.

Kehren wir zur Architektur der Anforderungsverarbeitung zurück:

Der Ereignis-Handler changeLocale.setFrenchLocale wurde ausgeführt und hat den Wert null an den Controller [Faces Servlet] zurückgegeben. Der Controller wird daher die Seite [index.xhtml] erneut anzeigen. Schauen wir uns das noch einmal an:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>

Jedes Mal, wenn ein Wert vom Typ #{msg['...']} ausgewertet wird, wird eine der Nachrichtendateien [messages.properties] verwendet. Es wird diejenige verwendet, die der „Lokalisierung“ der Seite entspricht (Zeile 6). Da der Ereignis-Handler changeLocale.setFrenchLocale diese Locale auf fr setzt, wird die Datei [messages_fr.properties] verwendet. Ein Klick auf den Link [English] (Zeile 14) ändert die Locale auf en (siehe die Methode changeLocale.setEnglishLocale). Die Datei [messages_en.properties] wird dann verwendet, und die Seite wird auf Englisch angezeigt:

Jedes Mal, wenn die Seite [index.xhtml] angezeigt wird, wird das <f:view>-Tag ausgeführt:


  <f:view locale="#{changeLocale.locale}">

und daher wird die Methode [ChangeLocale].getLocale() erneut ausgeführt. Da wir unserem Bean den Session-Gültigkeitsbereich zugewiesen haben:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

Die während einer Anfrage durchgeführte Lokalisierung wird für nachfolgende Anfragen beibehalten.

Es gibt noch ein letztes Element der Seite [index.xhtml], das untersucht werden muss:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      <h:form id="formulaire">
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
      </h:form>
    </body>
  </f:view>
</html>

Das <h:commandLink>-Tag in Zeile 17 hat ein action-Attribut, das auf eine Zeichenkette gesetzt ist. In diesem Fall wird kein Ereignis-Handler aufgerufen, um die Seite zu verarbeiten. Der Benutzer wird sofort auf die Seite [page1.xhtml] weitergeleitet. Schauen wir uns an, wie die Anwendung in diesem Anwendungsfall funktioniert:

Der Benutzer klickt auf den Link [Seite 1]. Das Formular wird an den [Faces Servlet]-Controller gesendet. Der Controller erkennt in der empfangenen Anfrage, dass der Link [Seite 1] angeklickt wurde. Er untersucht das entsprechende Tag:


        <h:commandLink value="#{msg['welcome.page1']}" action="page1"/>

Dem Link ist kein Ereignis-Handler zugeordnet. Der [Faces Servlet]-Controller fährt sofort mit Schritt [3] oben fort und zeigt die Seite [page1.xhtml] an:

2.4.7. Die JSF-Seite [page1.xhtml]

Die Seite [page1.xhtml] sendet den folgenden Stream an den Browser des Clients:

 

Der Code, der diese Seite generiert, lautet wie folgt:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['page1.titre']}"/></title>
    </head>
    <body>
      <h1><h:outputText value="#{msg['page1.entete']}"/></h1>
      <h:form>
        <h:commandLink value="#{msg['page1.welcome']}" action="index"/>
      </h:form>
    </body>
  </f:view>
</html>

Auf dieser Seite gibt es nichts, was nicht bereits erklärt wurde. Der Leser sollte die Verbindung zwischen dem JSF-Code und der an den Browser des Clients gesendeten Seite herstellen. Der Link zurück zur Startseite:


        <h:commandLink value="#{msg['page1.welcome']}" action="index"/>

zeigt die Seite [index.xhtml] an.

2.4.8. Ausführen des Projekts

Unser Projekt ist nun fertiggestellt. Wir können es erstellen (Clean und Build):

  • Beim Erstellen des Projekts wird der Ordner [target] auf der Registerkarte [Dateien] angelegt. In diesem Ordner finden Sie das Projektarchiv [mv-jsf2-02-1.0-SNAPSHOT.war]. Dies ist das Archiv, das auf dem Server bereitgestellt wird;
  • in [WEB-INF/classes] [2] finden Sie die kompilierten Klassen aus dem Ordner [Source Packages] des Projekts sowie die Dateien, die sich im Zweig [Other Sources] befanden, hier die Meldungsdateien,
  • in [WEB-INF/lib] [3] finden Sie die Projektbibliotheken,
  • im Stammverzeichnis von [WEB-INF] [4] finden Sie die Konfigurationsdateien des Projekts,
  • im Stammverzeichnis des Archivs [5] finden Sie die JSF-Seiten, die sich im Zweig [Web Pages] des Projekts befanden,
  • Sobald das Projekt erstellt ist, kann es ausgeführt werden [6]. Es wird gemäß seiner Laufzeitkonfiguration ausgeführt [7],
  • Der Tomcat-Server wird gestartet, falls er noch nicht lief [8],
  • wird das Archiv [mv-jsf2-02-1.0-SNAPSHOT.war] auf den Server geladen. Dies wird als Deployment des Projekts auf den Anwendungsserver bezeichnet,
  • in [9] werden Sie aufgefordert, bei der Ausführung einen Browser zu starten. Der Browser fordert den Anwendungskontext [10] an, d. h. die URL [http://localhost:8080/mv-jsf2-02]. Gemäß den Regeln in der Datei [web.xml] (siehe Seite 44) wird die Datei [faces/index.xhtml] an den Browser des Clients ausgeliefert. Da die URL die Form [/faces/*] hat, wird sie vom [Faces Servlet]-Controller verarbeitet (siehe [web.xml] auf Seite 44). Dieser Controller verarbeitet die Seite und sendet die folgende HTML-Ausgabe:
 
  • Der [Faces Servlet]-Controller verarbeitet dann die Ereignisse, die auf dieser Seite auftreten.

2.4.9. Die Konfigurationsdatei [faces-config.xml]

Wir haben die folgende [faces-config.xml]-Datei verwendet:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>

Dies ist die minimale Dateistruktur für eine internationalisierte JSF 2-Anwendung. Hier haben wir neue Funktionen von JSF 2 im Vergleich zu JSF 1 genutzt:

  • Beans und deren Gültigkeitsbereich mithilfe der Annotationen @ManagedBean, @RequestScoped, @SessionScoped und @ApplicationScoped deklarieren,
  • Navigieren zwischen Seiten unter Verwendung der Namen der XHTML-Seiten (ohne die Endung .xhtml) als Navigationsschlüssel.

Sie können diese Funktionen auch nicht verwenden und stattdessen diese JSF-Projektelemente in [faces-config.xml] wie in JSF 1 deklarieren. In diesem Fall könnte die Datei [faces-config.xml] wie folgt aussehen:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<!-- application -->
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
  
  <!-- managed beans -->
  <managed-bean>
    <managed-bean-name>changeLocale</managed-bean-name>
    <managed-bean-class>utils.ChangeLocale</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
 
   <!-- navigation -->
  <navigation-rule>
    <description/>
    <from-view-id>/index.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>p1</from-outcome>
      <to-view-id>/page1.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>
 
  <navigation-rule>
    <description/>
    <from-view-id>/page1.xhtml</from-view-id>
    <navigation-case>
      <from-outcome>welcome</from-outcome>
      <to-view-id>/index.xhtml</to-view-id>
    </navigation-case>
  </navigation-rule>
 
 
</faces-config>
  • Zeilen 20–24: Deklaration des changeLocale-Beans:
    • Zeile 21: Name des Beans;
    • Zeile 22: Vollständiger Name der mit dem Bean verbundenen Klasse;
    • Zeile 23: Geltungsbereich des Beans. Mögliche Werte sind „request“, „session“, „application“,
  • Zeilen 27–34: Deklaration einer Navigationsregel:
    • Zeile 28: Die Regel kann beschrieben werden. Hier haben wir dies nicht getan;
    • Zeile 29: die Seite, von der aus die Navigation beginnt (Ausgangspunkt);
    • Zeilen 30–33: ein Navigationsfall. Es können mehrere vorhanden sein;
    • Zeile 31: Der Navigationsschlüssel;
    • Zeile 32: die Seite, zu der navigiert wird.

Navigationsregeln können anschaulicher dargestellt werden. Beim Bearbeiten der Datei [faces-config.xml] können Sie die Registerkarte [PageFlow] verwenden:

 

Angenommen, wir verwenden die vorherige [faces-config.xml]-Datei. Wie würde sich unsere Anwendung ändern?

  • In der Klasse [ChangeLocale] würden die Annotationen @ManagedBean und @SessionScoped wegfallen, da die Bean nun in [faces-config] deklariert ist,
  • Die Navigation von [index.xhtml] zu [page1.xhtml] über einen Link würde wie folgt aussehen:

        <h:commandLink value="#{msg['welcome.page1']}" action="p1"/>

Dem Attribut „action“ wird der in [faces-config] definierte Navigationsschlüssel p1 zugewiesen.

  • Die Navigation von [page1.xhtml] zu [index.xhtml] über einen Link würde wie folgt aussehen:

        <h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>

Wir weisen dem Attribut action den in [faces-config] definierten Navigationsschlüssel </span>**<span style="color: #000000">welcome</span>**<span style="color: #000000"> zu;

  • die Methoden `setFrenchLocale` und `setEnglishLocale`, die einen Navigationsschlüssel zurückgeben müssen, müssen nicht geändert werden, da sie zuvor `null` zurückgaben, um anzuzeigen, dass der Benutzer auf derselben Seite blieb.

2.4.10. Fazit

Kehren wir zu dem von uns erstellten NetBeans-Projekt zurück:

Dieses Projekt folgt der folgenden Architektur:

In jedem JSF-Projekt finden wir die folgenden Elemente:

  • JSF-Seiten [A], die vom Controller [Faces Servlet] [3] an die Browser der Clients gesendet werden [4],
  • Sprachdateien [C], mit denen Sie die Sprache der JSF-Seiten ändern können,
  • Java-Klassen [B], die Ereignisse im Client-Browser [2a, 2b] verarbeiten und/oder als Modelle für die JSF-Seiten dienen [3]. Meistens werden die [Business-] und [DAO-]Schichten separat entwickelt und getestet. Die [Web-]Schicht wird dann mit einer Dummy-[Business-]Schicht getestet. Wenn die [Business-] und [DAO-]Schichten verfügbar sind, arbeiten wir in der Regel mit deren .jar-Dateien.
  • Konfigurationsdateien [D], um diese verschiedenen Elemente miteinander zu verknüpfen. Die Datei [web.xml] wurde auf Seite 44 beschrieben und wird nur selten geändert. Dasselbe gilt für [faces-config], wo wir immer die vereinfachte Version verwenden.

2.5. Beispiel mv-jsf2-03: Eingabeformular – JSF-Komponenten

Ab sofort werden wir den Aufbau des Projekts nicht mehr zeigen. Wir stellen fertige Projekte vor und erklären, wie sie funktionieren. Der Leser kann alle Beispiele von der Website dieses Dokuments herunterladen (siehe Abschnitt 1.2).

2.5.1. Die Anwendung

Die Anwendung verfügt über eine einzige Ansicht:

Die Anwendung zeigt die wichtigsten JSF-Komponenten, die in einem Eingabeformular verwendet werden können:

  • Spalte [1] gibt den Namen des verwendeten JSF/HTML-Tags an,
  • Spalte [2] zeigt ein Beispiel für die Dateneingabe für jedes der verwendeten Tags,
  • Spalte [3] zeigt die Werte des Beans an, der als Modell der Seite dient,
  • die in [2] vorgenommenen Eingaben werden über die Schaltfläche [4] validiert. Diese Validierung aktualisiert lediglich die Modell-Bean der Seite. Anschließend wird dieselbe Seite zurückgegeben. Somit zeigt Spalte [3] nach der Validierung die neuen Werte der Modell-Bean an, sodass der Benutzer die Auswirkungen seiner Eingaben auf das Modell der Seite überprüfen kann.

2.5.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

  • in [1] die JSF-Projektkonfigurationsdateien,
  • in [2] die einzige Seite des Projekts: index.xhtml,
  • in [3] ein Stylesheet [styles.css] zur Gestaltung der Seite [index.xhtml]
  • in [4] die Java-Klassen des Projekts,
  • in [5] die Meldungsdatei der Anwendung in zwei Sprachen: Französisch und Englisch.

2.5.3. Die Datei [pom.xml]

Wir zeigen hier nur die Abhängigkeiten:


    <dependencies>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>6.0</version>
            <scope>provided</scope>
        </dependency>
</dependencies>

Dies sind die für ein JSF-Projekt erforderlichen Abhängigkeiten. In den folgenden Beispielen wird diese Datei nur angezeigt, wenn sie sich ändert.

2.5.4. Die Datei [ web.xml]

Die Datei [web.xml] wurde so konfiguriert, dass die Seite [index.xhtml] die Startseite des Projekts ist:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/index.xhtml</welcome-file>
  </welcome-file-list>
</web-app>
  • Zeile 30: Die Seite [index.xhtml] ist die Startseite,
  • Zeilen 11–14: ein Parameter für das [Faces Servlet]. Er fordert an, dass Kommentare in einem Facelet wie:

        <!-- langues -->

ignoriert werden. Ohne diesen Parameter verursachen die Kommentare schwer nachvollziehbare Probleme,

  • Zeilen 3–6: ein Parameter für das [Faces Servlet], der etwas später erläutert wird.

2.5.5. Die Datei [faces-config.xml]

Die Datei [faces-config.xml] der Anwendung sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
</faces-config>
  • Zeilen 11–16: Konfigurieren Sie die Meldungsdatei der Anwendung.

2.5.6. Die Meldungsdatei [messages.properties]

Die Meldungsdateien (siehe [5] im Projekt-Screenshot) lauten wie folgt:

[messages_fr.properties]


form.langue1=Fran\u00e7ais
form.langue2=Anglais
form.titre=Java Server Faces - les tags
form.headerCol1=Type
form.headerCol2=Champs de saisie
form.headerCol3=Valeurs du modèle de la page
form.loginPrompt=login : 
form.passwdPrompt=mot de passe : 
form.descPrompt=description : 
form.selectOneListBox1Prompt=choix unique : 
form.selectOneListBox2Prompt=choix unique : 
form.selectManyListBoxPrompt=choix multiple : 
form.selectOneMenuPrompt=choix unique : 
form.selectManyMenuPrompt=choix multiple : 
form.selectBooleanCheckboxPrompt=marié(e) : 
form.selectManyCheckboxPrompt=couleurs préférées : 
form.selectOneRadioPrompt=moyen de transport préféré : 
form.submitText=Valider
form.buttonRazText=Raz

Diese Meldungen werden an folgenden Stellen auf der Seite angezeigt:

Die englische Version der Meldungen lautet wie folgt:

[messages_en.properties]


form.langue1=French
form.langue2=English
form.titre=Java Server Faces - the tags
form.headerCol1=Input Type
form.headerCol2=Input Fields
form.headerCol3=Page Model Values
form.loginPrompt=login : 
form.passwdPrompt=password : 
form.descPrompt=description : 
form.selectOneListBox1Prompt=unique choice : 
form.selectOneListBox2Prompt=unique choice : 
form.selectManyListBoxPrompt=multiple choice : 
form.selectOneMenuPrompt=unique choice : 
form.selectManyMenuPrompt=multiple choice : 
form.selectBooleanCheckboxPrompt=married : 
form.selectManyCheckboxPrompt=preferred colors : 
form.selectOneRadioPrompt=preferred transport means : 
form.submitText=Submit
form.buttonRazText=Reset

2.5.7. Das [Form.java]-Modell für die Seite [index.xhtml]

Im obigen Projekt dient die Klasse [Form.java] als Modell oder Backing Bean für die JSF-Seite [index.xhtml]. Veranschaulichen wir dieses Konzept eines Modells anhand eines Beispiels aus der Seite [index.xhtml]:


<!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
          <h:outputText value="#{form.inputText}"/>

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, generiert der obige Code Zeile 2 der Eingabetabelle:

Zeile 2 zeigt Feld [1] an, die Zeilen 3–6 zeigen Feld [2] an und Zeile 7 zeigt Feld [3] an.

In den Zeilen 5 und 7 wird ein EL-Ausdruck verwendet, der wie folgt auf die in der Klasse [Form.java] definierte Formular-Bean verweist:


package forms;
 
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
 
 
@ManagedBean
@RequestScoped
public class Form {
  • Zeile 7 definiert eine unbenannte Bean. Dies ist daher der Name der Klasse, der mit einem Kleinbuchstaben beginnt: form,
  • Die Bean hat einen Request-Scope. Das bedeutet, dass sie in einem Client-Request-/Server-Response-Zyklus instanziiert wird, wenn der Request sie benötigt, und zerstört wird, sobald die Antwort an den Client zurückgesendet wurde.

Im folgenden Code aus der Seite [index.xhtml]:


<!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

In den Zeilen 5 und 7 wird der Wert „inputText“ aus dem Formular-Bean verwendet. Um die Zusammenhänge zwischen einer Seite P und ihrem Modell M zu verstehen, müssen wir auf den für eine Webanwendung charakteristischen Zyklus aus Client-Anfrage und Server-Antwort zurückkommen ( ):

Wir müssen unterscheiden zwischen dem Fall, in dem die Seite P als Antwort an den Browser gesendet wird (Schritt 4) – beispielsweise bei der ersten Anforderung der Seite – und dem Fall, in dem der Benutzer ein Ereignis auf Seite P auslöst, das dann vom [Faces Servlet]-Controller verarbeitet wird (Schritt 1).

Wir können diese beiden Fälle unterscheiden, indem wir sie aus der Perspektive des Browsers betrachten:

  1. Bei der ersten Anfrage nach der Seite führt der Browser eine GET-Operation für die URL der Seite durch.
  2. beim Absenden von auf der Seite eingegebenen Werten führt der Browser eine POST-Operation für die URL der Seite durch.

In beiden Fällen wird dieselbe URL angefordert. Je nachdem, ob es sich bei der Anfrage des Browsers um einen GET- oder einen POST-Befehl handelt, wird die Anfrage unterschiedlich verarbeitet.

[Fall 1 – Erster Aufruf der Seite P]

Der Browser fordert die URL der Seite mit einer GET-Anfrage an. Der [Faces Servlet]-Controller fährt direkt mit Schritt [4] der Antwortdarstellung fort, und die Seite [index.xhtml] wird an den Client gesendet. Der JSF-Controller weist jedes Tag auf der Seite an, sich darzustellen. Nehmen wir das Beispiel von Zeile 5 des [index.xhtml]-Codes:


            <h:inputText id="inputText" value="#{form.inputText}"/>

Das JSF-Tag <h:inputText value="value"/> generiert das HTML-Tag <input type="text" value="value"/>. Die für die Verarbeitung dieses Tags zuständige Klasse stößt auf den Ausdruck #{form.inputText}, den sie auswerten muss:

  • Wenn die Formular-Bean noch nicht existiert, wird sie durch Instanziierung der Klasse forms.Form erstellt,
  • wird der Ausdruck #{form.inputText} durch Aufruf der Methode form.getInputText() ausgewertet,
  • Der Text <input id="form:inputText" type="text" name="form:inputText" value="text" /> wird in den HTML-Stream eingefügt, der an den Client gesendet wird, vorausgesetzt, die Methode form.getInputText() hat die Zeichenkette „text“ zurückgegeben. JSF weist der im Stream platzierten HTML-Komponente zudem einen Namen (name) zu. Dieser Name wird aus den ID-Bezeichnern der analysierten JSF-Komponente und denen ihrer übergeordneten Komponenten gebildet, in diesem Fall dem Tag <h:form id="form"/>.

Beachten Sie, dass, wenn Sie auf einer Seite P den Ausdruck #{M.field} verwenden, wobei M die Modell-Bean der Seite P ist, diese Bean über eine öffentliche getField()-Methode verfügen muss. Der von dieser Methode zurückgegebene Typ muss in einen String-Typ konvertierbar sein. Ein gängiges Modell M sieht wie folgt aus:

1
2
3
4
private T champ;
public T getChamp(){
    return champ;
} 

wobei T ein Typ ist, der in einen String konvertiert werden kann, möglicherweise mithilfe einer toString-Methode.

Immer noch in Bezug auf die Anzeige der Seite P, die Verarbeitung der Zeile:


<h:outputText value="#{form.inputText}"/>

ähnlich sein, und es wird die folgende HTML-Ausgabe generiert:

texte

Intern auf dem Server wird die Seite P als Komponentenbaum dargestellt, der den Tag-Baum der an den Client gesendeten Seite widerspiegelt. Wir bezeichnen diesen Baum als Seitenansicht oder “. Dieser Zustand wird gespeichert. Je nach Konfiguration in der Datei [web.xml] der Anwendung kann dies auf zwei Arten erfolgen:


<web-app ...>
...
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
...
</web-app>

Die Zeilen 7–11 definieren den [Faces Servlet]-Controller. Dieser kann mithilfe verschiedener <context-param>-Tags konfiguriert werden, darunter das in den Zeilen 3–6, das festlegt, dass der Status einer Seite auf dem Client (dem Browser) gespeichert werden muss. Der andere mögliche Wert in Zeile 5 ist **server**, was die Speicherung auf dem Server angibt. Dies ist der Standardwert.

Wenn der Status einer Seite auf dem Client gespeichert wird, fügt der JSF-Controller jeder von ihm gesendeten HTML-Seite ein verstecktes Feld hinzu, dessen Wert dem aktuellen Status der Seite entspricht. Dieses versteckte Feld hat die folgende Form:

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />

Sein Wert stellt in verschlüsselter Form den Zustand der an den Client gesendeten Seite dar. Es ist wichtig zu verstehen, dass dieses versteckte Feld Teil des Formulars der Seite ist und daher in den vom Browser gesendeten Werten enthalten ist, wenn das Formular übermittelt wird. Mithilfe dieses versteckten Feldes kann der JSF-Controller die Ansicht so wiederherstellen, wie sie an den Client gesendet wurde.

Wenn der Zustand einer Seite auf dem Server gespeichert wird, wird der an den Client gesendete Zustand der Seite in der Sitzung des Clients gespeichert. Wenn der Browser des Clients die im Formular eingegebenen Werte übermittelt, sendet er auch sein Sitzungstoken. Mithilfe dieses Tokens ruft der JSF-Controller den an den Client gesendeten Zustand der Seite ab und stellt ihn wieder her.

Die Kodierung des Zustands einer JSF-Seite kann mehrere hundert Bytes erfordern. Da dieser Zustand für jeden Benutzer der Anwendung aufrechterhalten wird, können bei einer großen Anzahl von Benutzern Speicherprobleme auftreten. Aus diesem Grund haben wir uns hier dafür entschieden, den Seitenzustand auf dem Client zu speichern (siehe [web.xml], Abschnitt 2.5.4, Seite 66).

[Fall 2 – Verarbeitung der Seite P]

Wir befinden uns bei Schritt [1] oben, wo der [Faces Servlet]-Controller eine POST-Anfrage vom Client-Browser erhält, an den er zuvor die Seite [index.xhtml] gesendet hat. Es handelt sich um die Verarbeitung eines Seitenereignisses. Bevor das Ereignis überhaupt in [2a] verarbeitet werden kann, müssen mehrere Schritte durchlaufen werden. Der Verarbeitungszyklus für eine POST-Anfrage durch den JSF-Controller sieht wie folgt aus:

Image

  • In [A] wird dank des versteckten Feldes `javax.faces.ViewState` die ursprünglich an den Client-Browser gesendete Ansicht rekonstruiert. Hier erhalten die Seitenkomponenten wieder die Werte, die sie in der gesendeten Seite hatten. Unsere `inputText`-Komponente erhält wieder ihren Wert „text“,
  • in [B] werden die vom Client-Browser gesendeten Werte verwendet, um die Ansichtskomponenten zu aktualisieren. Wenn der Benutzer also in das HTML-Eingabefeld namens `inputText` „jean“ eingegeben hat, ersetzt der Wert „jean“ den Wert „text“. Die Ansicht spiegelt nun die vom Benutzer geänderte Seite wider und nicht mehr die, die an den Browser gesendet wurde,
  • in [C] werden die gesendeten Werte überprüft. Angenommen, die vorherige inputText-Komponente ist ein Eingabefeld für das Alter. Der eingegebene Wert muss eine ganze Zahl sein. Vom Browser gesendete Werte sind immer vom Typ String. Ihr endgültiger Typ im M-Modell, das der Seite P zugeordnet ist, kann völlig anders sein. Es erfolgt dann eine Konvertierung vom Typ String in einen anderen Typ T. Diese Konvertierung kann fehlschlagen. In diesem Fall wird der Anfrage-/Antwortzyklus beendet, und die in [B] erstellte Seite P wird mit Fehlermeldungen an den Browser des Clients zurückgesendet, sofern der Autor der Seite P solche bereitgestellt hat. Beachten Sie, dass der Benutzer die Seite genau so sieht, wie er sie eingegeben hat, ohne dass der Entwickler dafür etwas tun muss. Bei einer anderen Technologie, wie beispielsweise JSP, muss der Entwickler die Seite P selbst anhand der vom Benutzer eingegebenen Werte neu erstellen. Der Wert einer Komponente kann zudem einem Validierungsprozess unterzogen werden. Um beim Beispiel der Komponente inputText, also dem Eingabefeld für das Alter, zu bleiben: Der eingegebene Wert muss nicht nur eine ganze Zahl sein, sondern eine ganze Zahl im Bereich [1,N]. Wenn der eingegebene Wert den Konvertierungsschritt besteht, kann er den Validierungsschritt dennoch nicht bestehen. In diesem Fall wird der Anfrage-/Antwortzyklus ebenfalls abgeschlossen, und die in [B] erstellte Seite P wird an den Client-Browser zurückgesendet,
  • in [D] werden die Werte aller Komponenten der Seite P dem M-Modell der Seite P zugewiesen, sofern diese die Konvertierungs- und Validierungsschritte erfolgreich durchlaufen haben. Wenn der Wert des Eingabefelds, das durch den folgenden Tag generiert wurde:

        <h:inputText value="#{form.inputText}"/>

„jean“ lautet, wird dieser Wert dem Formularmodell der Seite durch Ausführung des Codes form.setInputText("jean") zugewiesen. Beachten Sie, dass im Modell M der Seite P die privaten Felder von M, die den Wert eines Eingabefelds auf P speichern, über eine set-Methode verfügen müssen,

  • Sobald das Modell M der Seite P mit den übermittelten Werten aktualisiert wurde, kann das Ereignis verarbeitet werden, das den POST der Seite P ausgelöst hat. Dies ist Schritt [E]. Beachten Sie, dass der Handler für dieses Ereignis, sofern er zur Bean M gehört, Zugriff auf die Werte aus dem Formular P hat, die in den Feldern derselben Bean gespeichert wurden.
  • Schritt [E] gibt einen Navigationsschlüssel an den JSF-Controller zurück. In unseren Beispielen ist dies immer der Name der anzuzeigenden XHTML-Seite ohne die Endung .xhtml. Dies ist Schritt [F]. Ein anderer Ansatz besteht darin, einen Navigationsschlüssel zurückzugeben, der in der Datei [faces-config.xml] nachgeschlagen wird. Wir haben diesen Fall beschrieben.

Aus dem Vorstehenden können wir folgern, dass:

  • eine Seite P die Felder C ihres Modells M mithilfe der Methoden [M].getC() anzeigt,
  • die Felder C des Modells M auf einer Seite P werden mit den auf Seite P eingegebenen Werten mithilfe der Methoden [M].setC(input) initialisiert. In diesem Schritt können Konvertierungs- und Validierungsprozesse stattfinden, die fehlschlagen könnten. In diesem Fall wird das Ereignis, das den POST von Seite P ausgelöst hat, nicht verarbeitet, und die Seite wird genau so an den Client zurückgesendet, wie der Client sie eingegeben hat.

Das Modell [Form.java] für die Seite [index.xhtml] sieht wie folgt aus:


package forms;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
 
 
@ManagedBean
@RequestScoped
public class Form {
  
  /** Creates a new instance of Form */
  public Form() {
  }
  
  // form fields
  private String inputText="texte";
  private String inputSecret="secret";
  private String inputTextArea="ligne1\nligne2\n";
  private String selectOneListBox1="2";
  private String selectOneListBox2="3";
  private String[] selectManyListBox=new String[]{"1","3"};
  private String selectOneMenu="1";
  private String[] selectManyMenu=new String[]{"1","2"};
  private String inputHidden="initial";
  private boolean selectBooleanCheckbox=true;
  private String[] selectManyCheckbox=new String[]{"1","3"};
  private String selectOneRadio="2";
  
  // events
  public String submit(){
    return null;
  }
  
  // getters and setters
  ...
}

Die Felder in den Zeilen 16–27 werden in den folgenden Teilen des Formulars verwendet:

2.5.8. Die Seite [ index.xhtml]

Die Seite [index.xhtml], die die vorherige Ansicht generiert, sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- languages -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
          <!-- headers -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
          <!-- line 1 -->
          ...
          <!-- line 2 -->
          ...
          <!-- line 3 -->
          ...
          <!-- line 4 -->
          ...
          <!-- line 5 -->
          ...
          <!-- line 6 -->
          ...
          <!-- line 7 -->
          ...
          <!-- line 8 -->
          ...
          <!-- line 9 -->
          ...
          <!-- line 10 -->
          ...
          <!-- line 11 -->
          ...
          <!-- line 12 -->
          ...
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

Wir werden die Hauptkomponenten dieser Seite nacheinander untersuchen. Beachten Sie die allgemeine Struktur eines JSF-Formulars:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view ...>
    <h:head>
      ...
    </h:head>
    <h:body ...>
      <h:form id="formulaire">
        ...
        <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        ...
      </h:form>
    </h:body>
  </f:view>
</html>

Formularkomponenten müssen innerhalb eines <h:form>-Tags stehen (Zeilen 12–16). Das <f:view>-Tag (Zeilen 7–18) ist erforderlich, wenn die Anwendung internationalisiert ist. Außerdem muss ein Formular über eine Möglichkeit zum Absenden (POST) verfügen, häufig einen Link oder eine Schaltfläche wie in Zeile 14. Es kann auch durch verschiedene Ereignisse ausgelöst werden (Änderung einer Auswahl in einer Liste, Wechsel des aktiven Feldes, Eingabe eines Zeichens in ein Eingabefeld usw.).

2.5.9. Der Stil des Formulars

Um die Spalten der Formulartabelle besser lesbar zu machen, enthält das Formular ein Stylesheet:


  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
</h:head>
  • Zeile 4: Das Stylesheet der Seite wird innerhalb des HTML-Head-Tags mit dem folgenden Tag definiert:

<h:outputStylesheet library="css" name="styles.css"/>

Das Stylesheet befindet sich im Ordner [resources]:

Im Tag:


<h:outputStylesheet library="css" name="styles.css"/>
  • library ist der Name des Ordners, der das Stylesheet enthält,
  • name ist der Name des Stylesheets.

Sehen wir uns ein Beispiel für die Verwendung dieses Stylesheets an:


        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">

Das Tag <h:panelGrid columns="3"/> definiert ein dreispaltiges Raster. Das Attribut columnClasses wird verwendet, um diese Spalten zu gestalten. Die Werte col1, col2 und col3 im Attribut columnClasses legen die jeweiligen Stile für die Spalten 1, 2 und 3 des Rasters fest. Diese Stile werden im Stylesheet der Seite nachgeschlagen:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}
  • Zeilen 7–9: der Stil mit dem Namen col1,
  • Zeilen 11–13: der Stil mit dem Namen col2,
  • Zeilen 15–17: der Stil mit dem Namen col3,

Diese drei Stile legen die Hintergrundfarbe der jeweiligen Spalte fest.

  • Zeilen 19–23: Der Stil `entete` wird verwendet, um den Stil des Textes in der ersten Zeile der Tabelle zu definieren:

          <!-- entêtes -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
  • Zeilen 1–5: Der Stil „info“ wird verwendet, um den Stil des Textes in der ersten Spalte der Tabelle zu definieren:

          <!-- ligne 1 -->
          <h:outputText value="inputText"  styleClass="info"/>

Wir werden nicht näher auf die Verwendung von Stylesheets eingehen, da diese allein schon ein ganzes Buch füllen würden und ihre Entwicklung zudem oft Spezialisten überlassen wird. Dennoch haben wir uns für ein minimalistisches Stylesheet entschieden, um die Leser daran zu erinnern, dass deren Verwendung unerlässlich ist.

Schauen wir uns nun an, wie das Hintergrundbild der Seite definiert wurde:


<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">

Das Hintergrundbild wird über das style-Attribut des <h:body>-Tags festgelegt. Mit diesem Attribut können Sie Stilelemente festlegen. Das Hintergrundbild befindet sich im Ordner [resources/images/standard.jpg]:

Dieses Bild wird über die URL [/mv-jsf2-03/resources/images/standard.jpg] aufgerufen. Wir könnten daher schreiben:


<h:body style="background-image: url('mv-jsf2-03/resources/images/standard.jpg');">

/mv-jsf2-03 ist der Anwendungskontext. Dieser Kontext wird vom Webserver-Administrator festgelegt und kann sich daher ändern. Dieser Kontext kann mithilfe des EL-Ausdrucks ${request.contextPath} abgerufen werden. Daher bevorzugen wir das folgende style-Attribut:


style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"

das unabhängig vom Kontext gültig ist.

2.5.10. Die beiden Client-Anfrage-/Server-Antwort-Zyklen eines Formulars

Lassen Sie uns noch einmal auf das zurückkommen, was bereits in Abschnitt 2.5.7 für den allgemeinen Fall erläutert wurde, und es auf das vorliegende Formular anwenden. Dieses Formular wird in der Standard-JSF-Umgebung getestet:

Hier gibt es keine Ereignisbehandler und keine [Business]-Schicht. Die Schritte [2x] existieren daher nicht. Wir unterscheiden zwischen dem Fall, in dem das Formular F zunächst vom Browser angefordert wird, und dem Fall, in dem der Benutzer ein Ereignis im Formular F auslöst, das dann vom [Faces Servlet]-Controller verarbeitet wird. Es gibt zwei unterschiedliche Zyklen von Client-Anfragen und Server-Antworten.

  • Der erste, der der anfänglichen Seitenanforderung entspricht, wird durch eine GET-Anfrage des Browsers an die URL des Formulars ausgelöst,
  • der zweite, der der Übermittlung der auf der Seite eingegebenen Werte entspricht, wird durch eine POST-Anfrage an dieselbe URL ausgelöst.

Je nachdem, ob es sich bei der Anfrage des Browsers um einen GET- oder einen POST-Aufruf handelt, verarbeitet der [Faces Servlet]-Controller die Anfrage unterschiedlich.

[Fall 1 – Erste Anfrage für Formular F]

Der Browser fordert die URL der Seite mit einem GET an. Der [Faces Servlet]-Controller fährt direkt mit Schritt [4] der Darstellung der Antwort fort. Das Formular [index.xhtml] wird durch sein Modell [Form.java] initialisiert und an den Client gesendet, der die folgende Ansicht erhält:

Image

Der HTTP-Austausch zwischen Client und Server sieht in diesem Fall wie folgt aus:

HTTP-Anfrage des Clients:

1
2
3
4
5
6
7
8
GET /mv-jsf2-03/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en;q=0.6,en-us;q=0.4,es;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive

Zeile 1 zeigt die GET-Anfrage des Browsers.

HTTP-Antwort vom Server:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: JSF/2.0
Set-Cookie: JSESSIONID=F6E66136BF00EEE026ADAB1BBEBFD587; Path=/mv-jsf2-03/; HTTPOnly
Content-Type: text/html;charset=UTF-8
Content-Length: 7371
Date: Tue, 15 May 2012 09:04:57 GMT

Hier nicht dargestellt: Auf Zeile 7 folgen eine Leerzeile und der HTML-Code des Formulars. Dies ist der Code, den der Browser interpretiert und anzeigt.

[Fall 2 – Verarbeitung der in Formular F eingegebenen Werte]

Der Benutzer füllt das Formular aus und sendet es über die Schaltfläche [Absenden] ab. Der Browser sendet daraufhin eine POST-Anfrage an die URL des Formulars. Der [Faces Servlet]-Controller verarbeitet diese Anfrage, aktualisiert das [Form.java]-Modell für das Formular [index.xhtml] und gibt das aktualisierte Formular [index.xhtml] auf Basis dieses neuen Modells zurück. Betrachten wir diesen Ablauf anhand eines Beispiels:

Image

Oben hat der Benutzer seine Daten eingegeben und übermittelt. Als Antwort erhält er die folgende Ansicht:

Image

Der HTTP-Austausch zwischen Client und Server sieht in diesem Fall wie folgt aus:

HTTP-Anfrage des Clients:

POST /mv-jsf2-03/faces/index.xhtml HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en;q=0.6,en-us;q=0.4,es;q=0.2
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Referer: http://localhost:8080/mv-jsf2-03/faces/index.xhtml
Cookie: JSESSIONID=374CC5F1D2ACAC182A5747A443651E36
Content-Type: application/x-www-form-URLencoded
Content-Length: 1543

formulaire=formulaire&formulaire%3AinputText=nouveau+texte&formulaire%3AinputSecret=mdp&formulaire%3AinputTextArea=Tutoriel+JSF%0D%0A&formulaire%3AselectOneListBox1=3&formulaire%3AselectOneListBox2=5&formulaire%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire%3AselectManyListBox=5&formulaire%3AselectOneMenu=4&formulaire%3AselectManyMenu=5&formulaire%3AinputHidden=initial&formulaire%3AselectManyCheckbox=2&formulaire%3AselectManyCheckbox=3&formulaire%3AselectManyCheckbox=4&formulaire%3AselectOneRadio=4&formulaire%3Asubmit=Valider&javax.faces.ViewState=H4sIAAAAAAAAAJVUT0g...P4BKm1E4F0FAAA  

In Zeile 1 die vom Browser gesendete POST-Anfrage. In Zeile 14 die vom Benutzer eingegebenen Werte. Sie sehen beispielsweise den in das Eingabefeld eingegebenen Text:

formulaire%3AinputText=nouveau+texte

In Zeile 14 wurde das versteckte Feld javax.faces.ViewState gesendet. Dieses Feld repräsentiert in verschlüsselter Form den Zustand des Formulars, wie er ursprünglich bei der ersten GET-Anfrage an den Browser gesendet wurde.

HTTP-Antwort vom Server:

1
2
3
4
5
6
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Powered-By: JSF/2.0
Content-Type: text/html;charset=UTF-8
Content-Length: 7299
Date: Tue, 15 May 2012 09:37:17 GMT

Hier nicht dargestellt: Auf Zeile 6 folgen eine Leerzeile und der HTML-Code für das Formular, der durch die neue, aus der POST-Anfrage abgeleitete Vorlage aktualisiert wurde.

Wir werden nun die verschiedenen Komponenten dieses Formulars untersuchen.

2.5.11. <h:inputText>-Tag

Das <h:inputText>-Tag generiert ein HTML-Tag vom Typ <input type="text" ...>.

Betrachten Sie den folgenden Code:


          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

und die dazugehörige Vorlage [Form.java]:


  private String inputText="texte";
 
  public String getInputText() {
    return inputText;
  }
  
  public void setInputText(String inputText) {
    this.inputText = inputText;
}

Wenn die Seite [index.html] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1],
  • das <h:panelGroup>-Tag (Zeilen 3–6) ermöglicht es, mehrere Elemente innerhalb einer einzelnen Zelle der Tabelle zu gruppieren, die durch das <h:panelGrid>-Tag in Zeile 20 des vollständigen Codes der Seite generiert wird (siehe Abschnitt 2.5.8). Der Text [2] wird durch Zeile 4 generiert. Das Eingabefeld [3] wird durch Zeile [5] generiert. Hier wurde die Methode getInputText aus [Form.java] (Zeilen 3–5 des Java-Codes) verwendet, um den Text des Eingabefelds zu generieren,
  • Zeile 7 des XHTML-Codes generiert [4]. Auch hier wird die Methode getInputText aus [Form.java] verwendet, um den Text [4] zu generieren.

Die von der XHTML-Seite generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">inputText</span></td>
<td class="col2">login : <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /></td>
<td class="col3">texte</td>
</tr>

Die HTML-Tags <tr> und <td> werden durch das <h:panelGrid>-Tag generiert, das zur Erstellung der Formulartabelle verwendet wird.

Geben wir nun unten einen Wert in das Eingabefeld [1] ein und senden wir das Formular über die Schaltfläche [Submit] [2] ab. Als Antwort erhalten wir die folgende Seite [3, 4]:

Der Wert des Feldes [1] wird wie folgt übermittelt:

formulaire%3AinputText=nouveau+texte

In [2] wird das Formular über die folgende Schaltfläche abgeschickt:


          <h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>

Das <h:commandButton>-Tag hat kein action-Attribut. In diesem Fall wird kein Ereignis-Handler aufgerufen und keine Navigationsregel angewendet. Nach der Verarbeitung wird dieselbe Seite zurückgegeben. Sehen wir uns den Verarbeitungszyklus einmal an:

Image

  • In [A] wird die Seite P genau so wiederhergestellt, wie sie gesendet wurde. Das bedeutet, dass die Komponente mit der ID inputText mit ihrem Anfangswert „text“ wiederhergestellt wird.
  • in [B] werden die vom Browser gesendeten Werte (die vom Benutzer eingegeben wurden) den Komponenten der Seite P zugewiesen. Hier erhält die Komponente mit der ID inputText den Wert „new text“,
  • in [C] finden Konvertierungen und Validierungen statt. Hier gibt es keine. Im Modell M ist das Feld, das der Komponente mit der ID inputText zugeordnet ist, wie folgt:

private String inputText="texte";

Da die eingegebenen Werte vom Typ String sind, ist keine Konvertierung erforderlich. Außerdem wurden keine Validierungsregeln erstellt. Diese werden wir später erstellen.

  • In [D] werden die eingegebenen Werte dem Modell zugewiesen. Das Feld inputText in [Form.java] erhält den Wert „new text“,
  • in [E] passiert nichts, da dem [Validate]-Button kein Ereignis-Handler zugewiesen wurde.
  • In [F] wird die Seite P an den Client zurückgesendet, da die Schaltfläche [Validate] kein action-Attribut hat. Die folgenden Zeilen von [index.xhtml] werden dann ausgeführt:

          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputText}"/>

In den Zeilen 5 und 7 wird der Wert des Feldes „inputText“ des Modells verwendet, der nun „new text“ lautet. Dies führt zu folgender Anzeige:

2.5.12. <h:inputSecret>-Tag

Das <h:inputSecret>-Tag generiert ein HTML-Tag vom Typ <input type="password" ...>. Es handelt sich um ein Eingabefeld, das dem JSF-Tag <h:inputText> ähnelt, mit dem Unterschied, dass jedes vom Benutzer eingegebene Zeichen visuell durch ein Sternchen (*) ersetzt wird.

Betrachten Sie den folgenden Code:


          <!-- line 2 -->
          <h:outputText value="inputSecret"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.passwdPrompt']}"/>
            <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
          </h:panelGroup>
<h:outputText value="#{form.inputSecret}"/>

und die dazugehörige Vorlage in [Form.java]:


private String inputSecret="secret";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1]
  • Der Text [2] wird durch Zeile 4 generiert. Das Eingabefeld [3] wird durch Zeile [5] generiert. Normalerweise hätte die Methode getInputSecret in [Form.java] verwendet werden müssen, um den Text für das Eingabefeld zu generieren. Eine Ausnahme besteht, wenn das Eingabefeld vom Typ „password“ ist. Das <h:inputSecret>-Tag wird nur zum Lesen einer Eingabe verwendet, nicht zum Anzeigen.
  • Zeile 7 des XHTML-Codes generiert [4]. Hier wurde die Methode getInputSecret aus [Form.java] verwendet, um den Text [4] zu generieren (siehe Zeile 1 des Java-Codes).

Die von der XHTML-Seite generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">inputSecret</span></td>
<td class="col2">mot de passe : <input id="formulaire:inputSecret" type="password" name="formulaire:inputSecret" value="" /></td>
<td class="col3">secret</td>
</tr>
  • Zeile 3: Der HTML-Tag <input type="password" .../>, der vom JSF-Tag <h:inputSecret> generiert wird

Geben wir nun unten einen Wert in das Eingabefeld [1] ein und senden das Formular über die Schaltfläche [Submit] [2] ab. Als Antwort erhalten wir die folgende Seite [3]:

Der Wert des Feldes [1] wird wie folgt übermittelt:

formulaire%3AinputSecret=mdp

Das Absenden des Formulars über [2] führte dazu, dass das Modell [Form.java] mit dem Eintrag aus [1] aktualisiert wurde. Das Feld inputSecret in [Form.java] erhielt daraufhin den Wert „mdp“. Da das Formular [index.xhtml] keine Navigationsregeln oder Ereignisbehandler definiert hat, wird es nach der Aktualisierung seines Modells erneut angezeigt. Wir kehren dann zu der Ansicht zurück, die angezeigt wurde, als die Seite [index.xhtml] ursprünglich angefordert wurde, wobei sich nur der Wert des Feldes inputSecret des Modells geändert hat [3].

2.5.13. <h:inputTextArea>-Tag

Das <h:inputTextArea>-Tag generiert ein HTML-Tag <textarea ...>text</textarea>. Es handelt sich um ein Eingabefeld, das dem des JSF-Tags <h:inputText> ähnelt, mit dem Unterschied, dass hier mehrere Zeilen Text eingegeben werden können.

Betrachten Sie den folgenden Code:


          <!-- line 3 -->
          <h:outputText value="inputTextArea" styleClass="info"/>          
          <h:panelGroup>
            <h:outputText value="#{msg['form.descPrompt']}"/>
            <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
          </h:panelGroup>         
<h:outputText value="#{form.inputTextArea}"/>

und die dazugehörige Vorlage in [Form.java]:


private String inputTextArea="ligne1\nligne2\n";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1],
  • der Text [2] wird durch Zeile 4 generiert. Das Eingabefeld [3] wird durch Zeile [5] generiert. Sein Inhalt wurde durch den Aufruf der Methode getInputTextArea der Vorlage generiert, die den in Zeile 1 des obigen Java-Codes definierten Wert zurückgab,
  • Zeile 7 des XHTML-Codes generiert [4]. Hier wurde erneut die Methode getInputTextArea aus [Form.java] verwendet. Die Zeichenkette „line1\nline2“ enthielt \n-Zeilenumbrüche. Diese sind nach wie vor vorhanden. Beim Einfügen in einen HTML-Stream werden sie von Browsern jedoch als Leerzeichen dargestellt. Der HTML-Tag <textarea>, der [3] anzeigt, interpretiert die Zeilenumbrüche korrekt.

Die von der XHTML-Seite generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">inputTextArea</span></td>
<td class="col2">description : <textarea id="formulaire:inputTextArea" name="formulaire:inputTextArea" rows="4">ligne1
ligne2
</textarea></td>
<td class="col3">ligne1
ligne2
</td>
</tr>
  • Zeilen 3–5: Der HTML-Tag <textarea>...</textarea>, der vom JSF-Tag <h:inputTextArea> generiert wird

Geben wir nun unten einen Wert in das Eingabefeld [1] ein und senden wir das Formular über die Schaltfläche [Absenden] [2] ab. Daraufhin wird die folgende Seite angezeigt [3]:

Der Wert des Feldes [1], der übermittelt wurde, lautet wie folgt:

formulaire%3AinputTextArea=Tutoriel+JSF%0D%0Apartie+1%0D%0A

Das Absenden des Formulars über [2] führte dazu, dass das Modell [Form.java] mit der Eingabe aus [1] aktualisiert wurde. Das textArea-Feld in [Form.java] erhielt daraufhin den Wert „JSF Tutorial\npart1“. Ein Neuladen von [index.xhtml] zeigt, dass das textArea-Feld des Modells tatsächlich aktualisiert wurde [3].

2.5.14. <h:selectOneListBox>-Tag

Das <h:selectOneListBox>-Tag generiert ein HTML-Tag <select>...</select>. Optisch erzeugt es eine Dropdown-Liste oder eine Liste mit einer Bildlaufleiste.

Betrachten Sie den folgenden Code:


<!-- line 4 -->
          <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox1}"/>

und die dazugehörige Vorlage in [Form.java]:


private String selectOneListBox1="2";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1]
  • Der Text [2] wird durch Zeile 4 generiert. Die Dropdown-Liste [3] wird durch die Zeilen [5–9] generiert. Es ist der Wert des Attributs size="1", der bewirkt, dass die Liste nur einen Eintrag anzeigt. Fehlt dieses Attribut, ist der Standardwert des size-Attributs 1. Die Listenelemente wurden durch die <f:selectItem>-Tags in den Zeilen 6–8 generiert. Diese Tags haben die folgende Syntax:

<f:selectItem itemValue="valeur" itemLabel="texte"/>

Der Wert des Attributs itemLabel ist das, was in der Liste angezeigt wird. Der Wert des Attributs itemValue ist der Wert des Elements. Dies ist der Wert, der an den [Faces Servlet]-Controller gesendet wird, wenn das Element aus der Dropdown-Liste ausgewählt wird.

Das in [3] angezeigte Element wurde durch den Aufruf der Methode getSelectOneListBox1() (Zeile 5) bestimmt. Das erhaltene Ergebnis „2“ (Zeile 1 des Java-Codes) führte dazu, dass das Element in Zeile 7 der Dropdown-Liste angezeigt wurde, da dessen Attribut itemValue den Wert „2“ hat,

  • Zeile 11 des XHTML-Codes erzeugt [4]. Hier wurde erneut die Methode getSelectOneListBox1 aus [Form.java] verwendet.

Die von der XHTML-Seite generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">selectOneListBox (size=1)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox1" name="formulaire:selectOneListBox1" size="1">
    <option value="1">un</option>
    <option value="2" selected="selected">deux</option>
    <option value="3">trois</option>
</select></td>
<td class="col3">2</td>
</tr>
  • Zeilen 3 und 7: der HTML-Tag <select ...>...</select>, der durch den JSF-Tag <h:selectOneListBox> generiert wird,
  • Zeilen 4–6: die HTML-Tags <option ...> ... </option>, die von den JSF-Tags <f:selectItem> generiert werden,
  • Zeile 5: Die Tatsache, dass das Element mit value="2" in der Liste ausgewählt ist, spiegelt sich im Vorhandensein des Attributs selected="selected" wider.

Wählen wir nun unten [1] einen neuen Wert aus der Liste aus und senden wir das Formular über die Schaltfläche [Submit] [2] ab. Als Antwort erhalten wir die folgende Seite [3]:

Der Wert des übermittelten Feldes [1] lautet wie folgt:

formulaire%3AselectOneListBox1=3

Das Absenden des Formulars über [2] führte dazu, dass das Modell [Form.java] mit dem Eintrag [1] aktualisiert wurde. Das HTML-Element


    <option value="3">trois</option>

wurde ausgewählt. Der Browser hat die Zeichenfolge „3“ als Wert der JSF-Komponente gesendet, die die Dropdown-Liste generiert hat:


            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">

Der JSF-Controller verwendet die Methode setSelectOneListBox1("3"), um das Modell der Dropdown-Liste zu aktualisieren. Außerdem wird nach dieser Aktualisierung das Modellfeld [Form.java]


        private String selectOneListBox1;

enthält nun den Wert „3“.

Wenn die Seite [index.xhtml] nach der Verarbeitung erneut angezeigt wird, bewirkt dieser Wert die oben gezeigte Anzeige [3,4]:

  • Er bestimmt, welcher Eintrag der Dropdown-Liste angezeigt werden soll [3],
  • und der Wert des Feldes selectOneListBox1 wird in [4] angezeigt.

Betrachten wir eine Variante des <h:selectOneListBox>-Tags:


<!-- line 5 -->
          <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox2}"/>

Die Vorlage in [Form.java] für das <h:selectOneListBox>-Tag in Zeile 5 lautet wie folgt:


  private String selectOneListBox2="3";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes erzeugt [1],
  • der Text [2] wird durch Zeile 4 generiert. Die Liste mit einer Bildlaufleiste [3] wird durch die Zeilen [5–11] generiert. Es ist der Wert des Attributs size="3", der dazu führt, dass eine Liste mit einer Bildlaufleiste anstelle einer Dropdown-Liste entsteht. Die Listenelemente wurden durch die <f:selectItem>-Tags in den Zeilen 6–8 generiert,

Das in [3] ausgewählte Element wurde durch den Aufruf der Methode getSelectOneListBox2() (Zeile 5) ermittelt. Das erhaltene Ergebnis „3“ (Zeile 1 des Java-Codes) führte dazu, dass das Element in Zeile 8 der Liste angezeigt wurde, da dessen Attribut „itemValue“ den Wert „3“ hat,

  • Zeile 13 des XHTML-Codes generiert [4]. Hier wurde erneut die Methode getSelectOneListBox2 aus [Form.java] verwendet.

Die von der XHTML-Seite generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">selectOneListBox (size=3)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox2" name="formulaire:selectOneListBox2" size="3">
    <option value="1">un</option>
    <option value="2">deux</option>
    <option value="3" selected="selected">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select></td>
<td class="col3">3</td>
</tr>
  • Zeile 6: Die Tatsache, dass das Element mit value="3" in der Liste ausgewählt ist, führt dazu, dass das Attribut selected="selected" vorhanden ist.

Wählen wir nun unten [1] einen neuen Wert aus der Liste aus und senden wir das Formular über die Schaltfläche [Submit] [2] ab. Als Antwort erhalten wir die folgende Seite [3]:

Der für Feld [1] angegebene Wert lautet wie folgt:

formulaire%3AselectOneListBox2=5

Das Absenden des Formulars über [2] führte dazu, dass die Vorlage [Form.java] mit den in [1] eingegebenen Daten aktualisiert wurde. Das HTML-Element


    <option value="5">cinq</option>

wurde ausgewählt. Der Browser übermittelte die Zeichenfolge „5“ als Wert der JSF-Komponente, die die Dropdown-Liste generierte:


            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">

Der JSF-Controller verwendet die Methode setSelectOneListBox2("5"), um das Listenmodell zu aktualisieren. Außerdem wird nach dieser Aktualisierung das Feld


        private String selectOneListBox2;

den Wert „5“.

Wenn die Seite [index.xhtml] nach der Verarbeitung erneut angezeigt wird, bewirkt dieser Wert die oben in [3,4] gezeigte Darstellung:

  • Er bestimmt, welches Listenelement ausgewählt werden soll [3],
  • und der Wert des Feldes selectOneListBox2 wird in [4] angezeigt.

2.5.15. <h:selectManyListBox>-Tag

Das <h:selectManyListBox>-Tag generiert ein HTML-Tag <select multiple="multiple">...</select>, das es dem Benutzer ermöglicht, mehrere Elemente aus einer Liste auszuwählen.

Betrachten Sie den folgenden Code:


<!-- line 6 -->
          <h:outputText value="selectManyListBox (size=3)"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectManyListbox>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyListBoxValue}"/>

und die dazugehörige Vorlage in [Form.java]:


private String[] selectManyListBox=new String[]{"1","3"};

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1]
  • Der Text [2] wird durch Zeile 4 generiert. Die Liste [3] wird durch die Zeilen [5–11] generiert. Das Attribut size="3" bewirkt, dass die Liste zu jedem Zeitpunkt drei dieser Elemente anzeigt. Die in der Liste ausgewählten Elemente wurden durch den Aufruf der Methode getSelectManyListBox() (Zeile 5) des Java-Modells bestimmt. Das Ergebnis {"1","3"} (Zeile 1 des Java-Codes) ist ein Array aus String-Elementen. Jedes dieser Elemente wird verwendet, um eines der Listenelemente auszuwählen. Hier werden die Elemente in den Zeilen 6 und 10 ausgewählt, deren itemValue-Attribut im Array {"1","3"} enthalten ist. Dies wird in [3] gezeigt.
  • Zeile 14 des XHTML-Codes erzeugt [4]. Hier wird nicht die Methode `getSelectManyListBox` des Java-Listenmodells aufgerufen, sondern die folgende Methode `getSelectManyListBoxValue`:

private String[] selectManyListBox=new String[]{"1","3"};
  ...
  // getters et setters
  
  public String getSelectManyListBoxValue(){
    return getValue(selectManyListBox);
  }
  
  private String getValue(String[] chaines){
    String value="[";
    for(String chaine : chaines){
      value+=" "+chaine;
    }
    return value+"]";
  }

Hätten wir die Methode getSelectManyListBox aufgerufen, hätten wir ein Array von Strings erhalten. Um dieses Element in die HTML-Ausgabe einzubinden, hätte der Controller dessen toString-Methode aufgerufen. Bei einem Array gibt diese Methode jedoch nur den „Hashcode“ des Arrays zurück und nicht die Liste seiner Elemente, wie wir es gerne hätten. Daher verwenden wir die oben genannte Methode getSelectManyListBoxValue, um eine Zeichenkette zu erhalten, die den Inhalt des Arrays darstellt;

  • Zeile 12 des XHTML-Codes generiert die Schaltfläche [5]. Wenn diese Schaltfläche angeklickt wird, wird der JavaScript-Code im onclick-Attribut ausgeführt. Er wird in die HTML-Seite eingebettet, die vom JSF-Code generiert wird. Um dies zu verstehen, müssen wir die genaue Beschaffenheit dieser Seite kennen.

Die von der XHTML-Seite generierte HTML-Ausgabe sieht wie folgt aus:


<tr>
<td class="col1"><span class="info">selectManyListBox (size=3)</span></td>
<td class="col2">choix multiple : <select id="formulaire:selectManyListBox" name="formulaire:selectManyListBox" multiple="multiple" size="3">
    <option value="1" selected="selected">un</option>
    <option value="2">deux</option>
    <option value="3" selected="selected">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select>
            <p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </td>
<td class="col3">[ 1 3]</td>
</tr>
  • Zeilen 3 und 9: Der HTML-Tag <select multiple="multiple"...>...</select>, der vom JSF-Tag <h:selectManyListBox> generiert wird. Das Vorhandensein des Attributs „multiple“ zeigt an, dass es sich um eine Mehrfachauswahlliste handelt,
  • die Tatsache, dass das Listenmodell das String-Array {"1","3"} ist, bedeutet, dass die Listenelemente in Zeile 4 (value="1") und 6 (value="3") das Attribut selected="selected" haben,
  • Zeile 10: Wenn die Schaltfläche [Clear] angeklickt wird, wird der JavaScript-Code im Attribut onclick ausgeführt. Die Seite wird im Browser durch einen Baum von Objekten dargestellt, der oft als DOM (Document Object Model) bezeichnet wird. Auf jedes Objekt in diesem Baum kann der JavaScript-Code über dessen name-Attribut zugreifen. Die Liste in Zeile 3 des obigen HTML-Codes heißt formulaire:selectManyListBox. Auf das Formular selbst kann auf verschiedene Arten Bezug genommen werden. Hier wird es mit der Notation „this.form“ bezeichnet, wobei „this“ auf die Schaltfläche [Reset] verweist und „this.form“ auf das Formular, in dem sich diese Schaltfläche befindet. Die Liste „form:selectManyListBox“ befindet sich innerhalb desselben Formulars. Somit verweist die Notation „this.form['form:selectManyListBox']“ auf die Position der Liste im Komponentenbaum des Formulars. Das Objekt, das eine Liste darstellt, verfügt über ein selectedIndex-Attribut, dessen Wert der Index des ausgewählten Elements in der Liste ist. Dieser Index beginnt bei 0, um das erste Element in der Liste zu bezeichnen. Der Wert -1 gibt an, dass kein Element in der Liste ausgewählt ist. Der JavaScript-Code, der das selectedIndex-Attribut auf -1 setzt, hebt die Auswahl aller Elemente in der Liste auf, sofern welche ausgewählt waren.

Wählen wir nun unten [1] neue Werte aus der Liste aus (um mehrere Elemente in der Liste auszuwählen, halten Sie die Strg-Taste gedrückt, während Sie klicken) und senden Sie das Formular über die Schaltfläche [Submit] [2] ab. Als Antwort erhalten wir die folgende Seite [3,4]:

Der Wert des übermittelten Feldes [1] lautet wie folgt:

formulaire%3AselectManyListBox=3&formulaire%3AselectManyListBox=4&formulaire%3AselectManyListBox=5

Das Absenden des Formulars über [2] führte dazu, dass das Modell [Form.java] mit dem Eintrag [1] aktualisiert wurde. Die HTML-Elemente


    <option value="3">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>

wurden ausgewählt. Der Browser hat die drei Zeichenfolgen „3“, „4“, „5“ als Werte an die JSF-Komponente gesendet, die die Dropdown-Liste generiert hat:


            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">

Die Methode setSelectManyListBox des Modells wird verwendet, um dieses Modell mit den vom Browser gesendeten Werten zu aktualisieren:


  private String[] selectManyListBox;
....
  public void setSelectManyListBox(String[] selectManyListBox) {
    this.selectManyListBox = selectManyListBox;
}

In Zeile 3 sehen wir, dass der Methodenparameter ein Array von Strings ist. Hier handelt es sich um das Array {"3", "4", "5"}. Nach dieser Aktualisierung wird das Feld


        private String[] selectManyListBox;

enthält nun das Array {"3","4","5"}.

Wenn die Seite [index.xhtml] nach der Verarbeitung erneut angezeigt wird, bewirkt dieser Wert die Anzeige von [3,4] oben:

  • Er bestimmt, welche Elemente in der Liste ausgewählt werden sollen [3],
  • und der Wert des Feldes „selectManyListBox“ wird in [4] angezeigt.

2.5.16. <h:selectOneMenu>-Tag

Das <h:selectOneMenu>-Tag ist identisch mit dem <h:selectOneListBox size="1">-Tag. Im Beispiel wird folgender JSF-Code ausgeführt:


<!-- line 7 -->
          <h:outputText value="selectOneMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
            <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectOneMenu>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneMenu}"/>

Die Vorlage für das <h:selectOneMenu>-Tag in [Form.java] lautet wie folgt:


  private String selectOneMenu="1";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, generiert der obige Code die Ansicht:

Ein Beispiel für die Ausführung könnte wie folgt aussehen:

Der für Feld [1] angegebene Wert lautet wie folgt:

formulaire%3AselectOneMenu=4

2.5.17. <h:selectManyMenu>-Tag

Das <h:selectManyMenu>-Tag ist identisch mit dem <h:selectManyListBox size="1">-Tag. Der im Beispiel ausgeführte JSF-Code lautet wie folgt:


<!-- line 8 -->
          <h:outputText value="selectManyMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
            <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
              <f:selectItem itemValue="4" itemLabel="quatre"/>
              <f:selectItem itemValue="5" itemLabel="cinq"/>
            </h:selectManyMenu>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>

Die Vorlage für das <h:selectManyMenu>-Tag in [Form.java] lautet wie folgt:


    private String[] selectManyMenu=new String[]{"1","2"};

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, generiert der obige Code die Seite:

Die Liste [1] enthält die Texte „eins“, …, „fünf“, wobei die Elemente „eins“ und „zwei“ ausgewählt sind. Der generierte HTML-Code lautet wie folgt:


<tr>
<td class="col1"><span class="info">selectManyMenu</span></td>
<td class="col2"><span class="prompt">choix multiple : </span><select id="formulaire:selectManyMenu" name="formulaire:selectManyMenu" multiple="multiple" size="1">
    <option value="1" selected="selected">un</option>
    <option value="2" selected="selected">deux</option>
    <option value="3">trois</option>
    <option value="4">quatre</option>
    <option value="5">cinq</option>
</select>
            
            
            <p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </td>
<td class="col3"><span class="prompt">[ 1 2]</span></td>
</tr>

Wie oben in den Zeilen 4 und 5 gezeigt, sind die Elemente „one“ und „two“ ausgewählt (Vorhandensein des Attributs „selected“).

Es ist schwierig, einen Screenshot eines Beispiels dafür zu liefern, da wir die ausgewählten Elemente im Menü nicht anzeigen können. Leser werden gebeten, dies selbst auszuprobieren (um mehrere Elemente in der Liste auszuwählen, halten Sie die Strg-Taste gedrückt, während Sie klicken).

2.5.18. <h:inputHidden>-Tag

Das <h:inputHidden>-Tag hat keine visuelle Darstellung. Es dient ausschließlich dazu, ein HTML-Tag <input type="hidden" value="..."/> in den HTML-Fluss der Seite einzufügen. Wenn es innerhalb eines <h:form>-Tags enthalten ist, sind seine Werte Teil der Daten, die beim Absenden des Formulars an den Server gesendet werden. Da es sich um Formularfelder handelt, die der Benutzer nicht sehen kann, werden sie als versteckte Felder bezeichnet. Der Zweck dieser Felder besteht darin, Daten zwischen den verschiedenen Anfrage-/Antwortzyklen desselben Clients zu bewahren:

  • Der Client fordert ein Formular F an. Der Server sendet es und legt die Information I in einem versteckten Feld C ab, in der Form <h:inputHidden id="C" value="I"/>.
  • wenn der Client das Formular F ausgefüllt und an den Server übermittelt hat, wird der Wert I des Feldes C an den Server zurückgesendet. Der Server kann dann die Informationen I abrufen, die er auf der Seite gespeichert hatte. Dadurch entsteht ein Speicher zwischen den beiden Anfrage-/Antwortzyklen,
  • JSF selbst nutzt diese Technik. Die Information I, die es im Formular F speichert, ist der Wert aller seiner Komponenten. Dazu verwendet es das folgende versteckte Feld:

<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...8PswawAA" />

Das versteckte Feld heißt javax.faces.ViewState, und sein Wert ist eine Zeichenkette, die in verschlüsselter Form die Werte aller Komponenten auf der an den Client gesendeten Seite darstellt. Wenn der Client die Seite nach der Eingabe von Daten in das Formular absendet, wird das versteckte Feld „javax.faces.ViewState“ zusammen mit den eingegebenen Werten zurückgesendet. Dadurch kann der JSF-Controller die Seite so rekonstruieren, wie sie ursprünglich gesendet wurde. Dieser Mechanismus wurde auf Seite 72 erläutert.

Der JSF-Code für das Beispiel lautet wie folgt:


<!-- ligne 9 -->
          <h:outputText value="inputHidden"  styleClass="info"/>
          <h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
          <h:outputText value="#{form.inputHidden}"/>

Die Vorlage für das <h:inputHidden>-Tag in [Form.java] lautet wie folgt:


  private String inputHidden="initial";

Dies führt zu folgender Anzeige, wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird:

  • Zeile 2 erzeugt [1], Zeile 4 erzeugt [2]. Zeile 3 erzeugt kein visuelles Element.

Der generierte HTML-Code lautet wie folgt:


<tr>
<td class="col1"><span class="info">inputHidden</span></td>
<td class="col2"><input id="formulaire:inputHidden" type="hidden" name="formulaire:inputHidden" value="initial" /></td>
<td class="col3">initial</td>
</tr>

Wenn das Formular abgeschickt wird, wird der Wert „initial“ des Feldes mit dem Namen form:inputHidden in Zeile 3 zusammen mit den anderen Formularwerten übermittelt. Das Feld


  private String inputHidden;

wird mit diesem Wert aktualisiert, der bereits zu Beginn vorhanden war. Dieser Wert wird in die neue Seite aufgenommen, die an den Client zurückgesendet wird. Daher erhalten wir immer den oben gezeigten Screenshot.

Der für das versteckte Feld übermittelte Wert lautet wie folgt:

formulaire%3AinputHidden=initial

2.5.19. <h:selectBooleanCheckBox>-Tag

Das <h:selectBooleanCheckBox>-Tag generiert ein HTML-Tag <input type="checkbox" ...>.

Betrachten Sie den folgenden JSF-Code:


<!-- line 10 -->
  <h:outputText value="selectBooleanCheckbox" styleClass="info"/>
  <h:panelGroup>
    <h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}" styleClass="prompt" />
    <h:selectBooleanCheckbox id="selectBooleanCheckbox" value="#{form.selectBooleanCheckbox}"/>
  </h:panelGroup>
  <h:outputText value="#{form.selectBooleanCheckbox}"/>

Die Vorlage für das <h:selectBooleanCheckbox>-Tag in Zeile 5 oben in [Form.java] lautet wie folgt:


  private boolean selectBooleanCheckbox=true;

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1],
  • Der Text [2] wird durch Zeile 4 generiert. Das Kontrollkästchen [3] wird durch Zeile [5] generiert. Hier wurde die Methode „getSelectBooleanCheckbox“ aus [Form.java] verwendet, um das Kontrollkästchen zu aktivieren oder zu deaktivieren. Da die Methode einen booleschen Wert von „true“ zurückgibt (siehe Java-Code), wurde das Kontrollkästchen aktiviert,
  • Zeile 7 des XHTML-Codes generiert [4]. Auch hier wird die Methode getSelectBooleanCheckbox aus [Form.java] verwendet, um den Text [4] zu generieren.

Die durch den vorangehenden JSF-Code generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">selectBooleanCheckbox</span></td>
<td class="col2"><span class="prompt">mari&eacute;(e) : </span>
<input id="formulaire:selectBooleanCheckbox" type="checkbox" name="formulaire:selectBooleanCheckbox" checked="checked" /></td>
<td class="col3">true</td>
</tr>

In [4] sehen wir den generierten HTML-Tag <input type="checkbox">. Der Wert „true“ des zugehörigen Modells führte dazu, dass das Attribut checked="checked" zum Tag hinzugefügt wurde. Dadurch wird das Kontrollkästchen aktiviert.

Lassen Sie uns nun unten das Kontrollkästchen deaktivieren [1], das Formular absenden [2] und uns das Ergebnis ansehen [3, 4]:

Da das Kontrollkästchen deaktiviert ist, wird für das Feld [1] kein Wert übermittelt.

Das Absenden des Formulars über [2] führte dazu, dass das Modell [Form.java] durch die Eingabe [1] aktualisiert wurde. Das Feld selectBooleanCheckbox in [Form.java] erhielt daraufhin den Wert false. Das Neuladen von [index.xhtml] zeigt, dass das Feld „selectBooleanCheckbox“ im Modell tatsächlich aktualisiert wurde [3] und [4]. An dieser Stelle ist anzumerken, dass JSF dank des versteckten Feldes „javax.faces.ViewState“ feststellen konnte, dass das ursprünglich aktivierte Kontrollkästchen vom Benutzer deaktiviert worden war. Tatsächlich ist der Wert eines deaktivierten Kontrollkästchens nicht in den vom Browser übermittelten Werten enthalten. Dank des im versteckten Feld javax.faces.ViewState gespeicherten Komponentenbaums stellt JSF fest, dass sich im Formular ein Kontrollkästchen namens „selectBooleanCheckbox“ befand und dass dessen Wert nicht in den vom Client-Browser gesendeten Werten enthalten ist. Daraus kann JSF schließen, dass es im gesendeten Formular deaktiviert war, was es ermöglicht, dem zugehörigen Java-Modell den booleschen Wert „false“ zuzuweisen:


  private boolean selectBooleanCheckbox;

2.5.20. <h:selectManyCheckBox>-Tag

Das <h:selectManyCheckBox>-Tag generiert eine Gruppe von Kontrollkästchen und damit mehrere HTML-Tags vom Typ <input type="checkbox" ...>. Dieses Tag ist das Gegenstück zum <h:selectManyListBox>-Tag, mit dem Unterschied, dass die auswählbaren Elemente als nebeneinander angeordnete Kontrollkästchen statt als Liste dargestellt werden. Was zum <h:selectManyListBox>-Tag gesagt wurde, gilt auch hier.

Betrachten Sie den folgenden JSF-Code:


          <!-- line 11 -->
          <h:outputText value="selectManyCheckbox" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
            <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
              <f:selectItem itemValue="1" itemLabel="rouge"/>
              <f:selectItem itemValue="2" itemLabel="bleu"/>
              <f:selectItem itemValue="3" itemLabel="blanc"/>
              <f:selectItem itemValue="4" itemLabel="noir"/>
            </h:selectManyCheckbox>
          </h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>

Die Vorlage für das <h:selectManyCheckbox>-Tag in Zeile 5 oben in [Form.java] lautet wie folgt:


private String[] selectManyCheckbox=new String[]{"1","3"};

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Seite wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1],
  • Der Text [2] wird durch Zeile 4 generiert. Die Kontrollkästchen [3] werden durch die Zeilen 5–10 generiert. Für jedes davon gilt:
  • definiert das Attribut „itemLabel“ den Text, der neben dem Kontrollkästchen angezeigt wird;
  • das Attribut „itemvalue“ definiert den Wert, der an den Server gesendet wird, wenn das Kontrollkästchen aktiviert ist,

Das Modell für die vier Kontrollkästchen ist das folgende Java-Feld:


private String[] selectManyCheckbox=new String[]{"1","3"};

Dieses Array legt fest:

  • welche Kontrollkästchen bei der Anzeige der Seite aktiviert sein sollen. Dies geschieht über ihren Wert, d. h. ihr Feld „itemValue“. Im obigen Beispiel werden die Kontrollkästchen mit den Werten im Array {"1","3"} aktiviert. Dies ist auf dem obigen Screenshot zu sehen;
  • Wenn die Seite übermittelt wird, erhält das selectManyCheckbox-Modell das Array mit den Werten für die Kontrollkästchen, die der Benutzer markiert hat. Dies werden wir in Kürze sehen;
  • Zeile 12 des XHTML-Codes generiert [4]. Es ist die folgende Methode getSelectManyCheckboxValue, die [4] generiert hat:

  public String getSelectManyCheckboxValue(){
    return getValue(getSelectManyCheckbox());
  }
  
  private String getValue(String[] chaines){
    String value="[";
    for(String chaine : chaines){
      value+=" "+chaine;
    }
    return value+"]";
}

Die durch den vorherigen JSF-Code generierte HTML-Ausgabe lautet wie folgt:


    <tr>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:0" value="1" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:0"> rouge</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:1" value="2" type="checkbox" /><label for="formulaire:selectManyCheckbox:1"> bleu</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:2" value="3" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:2"> blanc</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:3" value="4" type="checkbox" /><label for="formulaire:selectManyCheckbox:3"> noir</label></td>
    </tr>
</table></td>
<td class="col3">[ 1 3]</td>
</tr>

Es wurden vier HTML-Tags <input type="checkbox" ...> generiert. Die Tags in den Zeilen 3 und 7 haben das Attribut checked="checked", wodurch sie als markiert angezeigt werden. Beachten Sie, dass sie alle das gleiche Attribut name="formulaire:selectManyCheckbox" haben; mit anderen Worten: Die vier HTML-Felder haben denselben Namen. Wenn die Kontrollkästchen in den Zeilen 5 und 9 vom Benutzer aktiviert werden, sendet der Browser die Werte der vier Kontrollkästchen im folgenden Format:

formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

und das Modell für die vier Kontrollkästchen


private String[] selectManyCheckbox=new String[]{"1","3"};

erhält das Array {"2","4"}.

Lassen Sie uns dies im Folgenden überprüfen. In [1] nehmen wir die Änderung vor; in [2] senden wir das Formular ab. In [3] das erhaltene Ergebnis:

Die für die Felder [1] übermittelten Werte lauten wie folgt:

formulaire%3AselectManyCheckbox=2&formulaire%3AselectManyCheckbox=4

2.5.21. <h:selectOneRadio>-Tag

Das <h:selectOneRadio>-Tag erzeugt eine Gruppe von sich gegenseitig ausschließenden Optionsfeldern.

Betrachten Sie den folgenden JSF-Code:


<!-- line 12 -->
          <h:outputText value="selectOneRadio" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
            <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
              <f:selectItem itemValue="1" itemLabel="voiture"/>
              <f:selectItem itemValue="2" itemLabel="vélo"/>
              <f:selectItem itemValue="3" itemLabel="scooter"/>
              <f:selectItem itemValue="4" itemLabel="marche"/>
            </h:selectOneRadio>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneRadio}"/>

Die Vorlage für das <h:selectOneRadio>-Tag in Zeile 5 oben sieht in [Form.java] wie folgt aus:


  private String selectOneRadio="2";

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, sieht die resultierende Ansicht wie folgt aus:

  • Zeile 2 des XHTML-Codes generiert [1],
  • der Text [2] wird durch Zeile 4 erzeugt. Die Optionsfelder [3] werden durch die Zeilen 5–10 erzeugt. Für jedes davon gilt:
  • definiert das Attribut „itemLabel“ den Text, der neben dem Optionsfeld angezeigt wird;
  • das Attribut „itemvalue“ definiert den Wert, der an den Server gesendet wird, wenn das Kontrollkästchen ausgewählt wird,

Das Modell für die vier Optionsfelder ist das folgende Java-Feld:


  private String selectOneRadio="2";

Dieses Modell definiert:

  • bei der Anzeige der Seite, welcher der einzelnen Radiobuttons ausgewählt werden muss. Dies geschieht über deren Wert, d. h. über das Feld „itemValue“. Im obigen Beispiel wird der Radiobutton mit dem Wert „2“ ausgewählt. Dies ist auf dem Screenshot oben zu sehen;
  • Wenn die Seite übermittelt wird, erhält die selectOneRadio-Vorlage den Wert des ausgewählten Radiobuttons. Wir werden dies in Kürze sehen;
  • Zeile 12 des XHTML-Codes generiert [4].

Die durch den vorangehenden JSF-Code generierte HTML-Ausgabe lautet wie folgt:


<tr>
<td class="col1"><span class="info">selectOneRadio</span></td>
<td class="col2">moyen de transport pr&eacute;f&eacute;r&eacute; : <table id="formulaire:selectOneRadio">
    <tr>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:0" value="1" /><label for="formulaire:selectOneRadio:0"> voiture</label></td>
<td>
<input type="radio" checked="checked" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:1" value="2" /><label for="formulaire:selectOneRadio:1"> v&eacute;lo</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:2" value="3" /><label for="formulaire:selectOneRadio:2"> scooter</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:3" value="4" /><label for="formulaire:selectOneRadio:3"> marche</label></td>
</tr>

Es wurden vier HTML-Tags <input type="radio" ...> generiert. Das Tag in Zeile 8 hat das Attribut checked="checked", wodurch das entsprechende Optionsfeld als ausgewählt angezeigt wird. Beachten Sie, dass alle Tags dasselbe Attribut name="form:selectOneRadio" haben, was bedeutet, dass die vier HTML-Felder denselben Namen teilen. Dies ist die Voraussetzung für eine Gruppe sich gegenseitig ausschließender Optionsfelder: Wenn eines ausgewählt ist, sind die anderen nicht ausgewählt.

Unten in [1] aktivieren wir eines der Optionsfelder; in [2] senden wir das Formular ab; in [3] das Ergebnis:

Der für Feld [1] übermittelte Wert lautet wie folgt:

formulaire%3AselectOneRadio=4

2.6. Beispiel mv-jsf2-04: dynamische Listen

2.6.1. Die Anwendung

Die Anwendung ist dieselbe wie zuvor:

Die einzigen Änderungen betreffen die Art und Weise, wie die Listenelemente für die Felder [1] und [2] generiert werden. Hier werden sie dynamisch durch Java-Code generiert, während sie in der vorherigen Version fest in die JSF-Seite eingebettet waren.

2.6.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Das Projekt [mv-jsf2-04] ist identisch mit dem Projekt [mv-jsf2-03], mit folgenden Unterschieden:

  • In [1] sind auf der JSF-Seite die Listenelemente nicht mehr fest im Code hinterlegt,
  • in [2] wird die Vorlage für die JSF-Seite [1] geändert,
  • in [3] wird eine der Meldungen geändert.

2.6.3. Die Seite [index.xhtml] und ihre Vorlage [Form.java]

Die JSF-Seite [index.xhtml] sieht nun wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <!-- languages -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
...
          <!-- line 4 -->
          <h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox1}"/>
          <!-- line 5 -->
          <h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
            <h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
              <f:selectItems value="#{form.selectOneListbox2Items}"/>
            </h:selectOneListbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneListBox2}"/>
          <!-- line 6 -->
          <h:outputText value="selectManyListBox (size=3)"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
            <h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
              <f:selectItems value="#{form.selectManyListBoxItems}"/>
            </h:selectManyListbox>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyListBoxValue}"/>
          <!-- line 7 -->
          <h:outputText value="selectOneMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
            <h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
              <f:selectItems value="#{form.selectOneMenuItems}"/>
            </h:selectOneMenu>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneMenu}"/>
          <!-- line 8 -->
          <h:outputText value="selectManyMenu" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
            <h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
              <f:selectItems value="#{form.selectManyMenuItems}"/>
            </h:selectManyMenu>
            <p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
...
          <!-- line 11 -->
          <h:outputText value="selectManyCheckbox" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
            <h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
              <f:selectItems value="#{form.selectManyCheckboxItems}"/>
            </h:selectManyCheckbox>
          </h:panelGroup>
          <h:outputText value="#{form.selectManyCheckboxValue}"/>
          <!-- line 12 -->
          <h:outputText value="selectOneRadio" styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
            <h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
              <f:selectItems value="#{form.selectOneRadioItems}"/>
            </h:selectOneRadio>
          </h:panelGroup>
          <h:outputText value="#{form.selectOneRadio}"/>
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

Die vorgenommenen Änderungen sind in den Zeilen 26–28 zu sehen. Zuvor hatten wir folgenden Code:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="1" itemLabel="un"/>
              <f:selectItem itemValue="2" itemLabel="deux"/>
              <f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>

Nun haben wir Folgendes:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>

Die drei <f:selectItem>-Tags in den Zeilen 2–4 wurden durch das einzelne <f:selectItems>-Tag in Zeile b ersetzt. Dieses Tag verfügt über ein value-Attribut, dessen Wert eine Sammlung von Elementen des Typs javax.faces.model.SelectItem ist. Oben, , wird der Wert des value-Attributs durch Aufruf der folgenden Methode [form].getSelectOneListbox1Items ermittelt:


  public SelectItem[] getSelectOneListbox1Items() {
    return getItems("A",3);
  }
 
  private SelectItem[] getItems(String label, int qte) {
    SelectItem[] items=new SelectItem[qte];
    for(int i=0;i<qte;i++){
      items[i]=new SelectItem(i,label+i);
    }
    return items;
}
  • In Zeile 1 gibt die Methode getSelectOneListbox1Items ein Array von Elementen des Typs javax.faces.model.SelectItem zurück, das von der privaten Methode getItems in Zeile 5 erstellt wurde. Beachten Sie, dass die Methode getSelectOneListbox1Items nicht der Getter für ein privates Feld namens selectOneListBox1Items ist;
  • die Klasse javax.faces.model.SelectItem verfügt über verschiedene Konstruktoren.

Image

In Zeile 8 der Methode **getItems** verwenden wir den Konstruktor SelectItem(Object value, String label), der dem JSF-Tag


    <f:selectItem itemValue="value" labelValue="label"/>
  • Zeilen 5–10: Die Methode getItems(String label, int qte) erstellt ein Array mit qte Elementen vom Typ SelectItem, wobei das i-te Element über den Konstruktor SelectItem(i, label+i) abgerufen wird.

Der JSF-Code


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>

entspricht dann funktional dem folgenden JSF-Code:


<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
              <f:selectItem itemValue="0" itemLabel="A0"/>
              <f:selectItem itemValue="1" itemLabel="A1"/>
              <f:selectItem itemValue="2" itemLabel="A2"/>
</h:selectOneListbox>

Das Gleiche gilt für alle anderen Listen auf der JSF-Seite. Die Vorlage [Form.java] enthält nun die folgenden neuen Methoden:


  public SelectItem[] getSelectOneListbox1Items() {
    return getItems("A",3);
  }
  
  public SelectItem[] getSelectOneListbox2Items() {
    return getItems("B",4);
  }
  
  public SelectItem[] getSelectManyListBoxItems() {
    return getItems("C",5);
  }
  
  public SelectItem[] getSelectOneMenuItems() {
    return getItems("D",3);
  }
  
  public SelectItem[] getSelectManyMenuItems() {
   return getItems("E",4);
   }
  
  public SelectItem[] getSelectManyCheckboxItems() {
   return getItems("F",3);
   }
  
  public SelectItem[] getSelectOneRadioItems() {
   return getItems("G",4);
   }
  
  private SelectItem[] getItems(String label, int qte) {
    SelectItem[] items=new SelectItem[qte];
    for(int i=0;i<qte;i++){
      items[i]=new SelectItem(i,label+i);
    }
    return items;
}

2.6.4. Die Meldungsdatei

Es wird nur eine Meldungsdatei geändert:

[messages_fr.properties]


form.titre=Java Server Faces - remplissage dynamique des listes

[messages_en.properties]


form.titre=Java Server Faces - dynamic filling of lists of elements

2.6.5. Tests

Leser sind eingeladen, diese neue Version zu testen.

Meistens sind die dynamischen Elemente eines Formulars das Ergebnis einer geschäftlichen Logikverarbeitung oder stammen aus einer Datenbank:

Betrachten wir die erste Anfrage für die JSF-Seite [index.xhtml] über eine GET-Anfrage des Browsers:

  • Die JSF-Seite wird angefordert [1],
  • der Controller [Faces Servlet] fordert ihre Anzeige in [3] an. Die JSF-Engine, die die Seite verarbeitet, ruft ihr Modell [Form.java] auf, beispielsweise die Methode getSelectOneListBox1Items. Diese Methode könnte durchaus ein Array von Elementen vom Typ SelectItem zurückgeben, basierend auf Informationen, die in einer Datenbank gespeichert sind. Dazu würde sie die [Business]-Schicht [2b] aufrufen.

2.7. Beispiel mv-jsf2-05: Navigation – Sitzung – Ausnahmebehandlung

2.7.1. Die Anwendung

Die Anwendung ist dieselbe wie zuvor, mit dem Unterschied, dass das Formular nun die Form eines mehrseitigen Assistenten hat:

  • in [1], Seite 1 des Formulars – ist auch über den Link 1 in [2] erreichbar
  • in [2], eine Gruppe von 5 Links.
  • in [3], Seite 2 des Formulars, aufgerufen über Link 2 in [2]
  • in [4], Seite 3 des Formulars, aufgerufen über Link 3 in [2]
  • in [5], die Seite, die über den Link „Ausnahme melden“ in [2] aufgerufen wurde
  • in [6], die Seite, die über Link 4 in [2] aufgerufen wird. Sie fasst die Einträge auf den Seiten 1 bis 3 zusammen.

2.7.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Das Projekt [mv-jsf2-05] führt zwei neue Funktionen ein:

  1. In [1] ist die JSF-Seite [index.xhtml] in drei Seiten [form1.xhtml, form2.xhtml, form3.xhtml] aufgeteilt, auf die die Einträge verteilt wurden. Die Seite [form4.xhtml] ist eine Kopie der Seite [index.xhtml] aus dem vorherigen Projekt. In [2] bleibt die Klasse [Form.java] unverändert. Sie dient als Vorlage für die vier oben genannten JSF-Seiten,
  2. In [3] wird eine Seite [exception.xhtml] hinzugefügt: Sie wird verwendet, wenn in der Anwendung eine Ausnahme auftritt.

2.7.3. Die [form.xhtml]-Seiten und ihre Vorlage [Form.java]

2.7.3.1. Der XHTML-Seiten-Code

Die JSF-Seite [form1.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
        <!-- links -->
        <h:panelGrid columns="2">
          <h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
          <h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
        </h:panelGrid>
        <h1><h:outputText value="#{msg['form1.titre']}"/></h1>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <!-- line 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
          <!-- line 2 -->
          <h:outputText value="inputSecret"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.passwdPrompt']}"/>
            <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
          </h:panelGroup>
          <!-- line 3 -->
          <h:outputText value="inputTextArea" styleClass="info"/>          
          <h:panelGroup>
            <h:outputText value="#{msg['form.descPrompt']}"/>
            <h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
          </h:panelGroup>         
        </h:panelGrid>
        <!-- links -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="#{form.doAction2}"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="#{form.doAction4}"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
        </h:panelGrid>
      </h:form>
      </h:body>
  </f:view>
</html>

und entspricht der folgenden Anzeige:

Beachten Sie folgende Punkte:

  • Zeile 16: Die Tabelle, die zuvor drei Spalten hatte, hat nun nur noch zwei. Spalte 3, in der die Modellwerte angezeigt wurden, wurde entfernt. Diese Werte werden von [form4.xhtml] angezeigt,
  • Zeilen 40–46: eine Tabelle mit sechs Links. Die Links in den Zeilen 44 und 46 verfügen über statische Navigation: Ihr action-Attribut ist fest codiert. Die anderen Links verfügen über dynamische Navigation: Ihr action-Attribut verweist auf eine Methode im Formular-Bean, die für die Rückgabe des Navigationsschlüssels zuständig ist. Die in [Form.java] referenzierten Methoden lauten wie folgt:

// événements
  public String doAction2(){
    return "form2";
  }
  
  public String doAction4(){
    return "form4";
  }
  
  public String doAlea(){
    // un nombre aléatoire entre 1 et 3
    int i=1+(int)(3*Math.random());
    // on rend la clé de navigation
    return "form"+i;
  }
  
  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

Die Methode throwException in Zeile 17 lassen wir vorerst außer Acht. Wir kommen später darauf zurück. Die Methoden doAction2 und doAction4 geben einfach den Navigationsschlüssel zurück, ohne eine Verarbeitung durchzuführen. Wir hätten genauso gut schreiben können:


<h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="form2"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="form4"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>

Die Methode doAlea generiert einen zufälligen Navigationsschlüssel, dessen Wert aus der Menge {"form1", "form2", "form3"} ausgewählt wird.

Der Code für die Seiten [form2.xhtml, form3.xhtml, form3.xhtml] ähnelt dem der Seite [form1.xhtml].

2.7.3.2. Lebensdauer des Modells [Form.java] für die Seiten [form*.xhtml]

Betrachten Sie die folgende Abfolge von Aktionen:

  • In [1] füllen wir Seite 1 aus und gehen zu Seite 3,
  • in [2] füllen wir Seite 3 aus und kehren zu Seite 1 zurück,
  • in [3] wird Seite 1 so abgerufen, wie sie eingegeben wurde. Wir kehren dann zu Seite 3 zurück,
  • in [4] wird Seite 3 genau so gefunden, wie sie eingegeben wurde.

Der Mechanismus des versteckten Feldes [javax.faces.ViewState] reicht nicht aus, um dieses Phänomen zu erklären.

Beim Übergang von [1] zu [2] finden mehrere Schritte statt:

  • Das Modell [Form.java] wird mit dem POST von [form1.jsp] aktualisiert. Insbesondere erhält das Feld inputText den Wert „another text“,
  • der Navigationsschlüssel „form3“ löst die Anzeige von [form3.xhtml] aus. Der in [form3.xhtml] eingebettete ViewState enthält nur den Zustand der Komponenten in [form3.xhtml], nicht den der Komponenten in [form1.xhtml].

Beim Übergang von [2] zu [3]:

  • wird das Modell [Form.java] mit dem POST aus [form3.xhtml] aktualisiert. Wenn der Lebenszyklus des Modells [Form.java] auf „request“ gesetzt ist, wird ein brandneues [Form.java]-Objekt erstellt, bevor es durch den POST aus [form3.xhtml] aktualisiert wird. In diesem Fall wird das Feld inputText des Modells auf seinen Standardwert zurückgesetzt:

  private String inputText="texte";

und behält diesen bei: Tatsächlich wird im POST von [form3.xhtml] das Feld inputText nicht aktualisiert, da es Teil des Modells [form1.xhtml] und nicht des Modells [form3.xhtml] ist.

  • Die Navigationsschaltfläche „form1“ bewirkt, dass [form1.xhtml] angezeigt wird. Die Seite zeigt ihre Vorlage an. In unserem Fall zeigt das mit der Vorlage inputText verknüpfte Anmeldefeld „text“ an und nicht den in [1] eingegebenen Wert „another text“. Damit das Feld „inputText“ den in [1] eingegebenen Wert beibehält, muss der Geltungsbereich der Vorlage [Form.java] „session“ und nicht „request“ lauten. In diesem Fall
    • wird das Modell nach der POST-Anfrage von [form1.xhtml] in die Sitzung des Clients gestellt. Das Feld inputText hat den Wert „some other text“;
    • Wenn [form3.xhtml] per POST gesendet wird, wird das Modell aus dieser Sitzung abgerufen und durch den POST von [form3.xhtml] aktualisiert. Das Feld inputText wird durch diesen POST nicht aktualisiert, sondern behält den Wert „some other text“ bei, der nach dem POST von [form1.xhtml] [1] übernommen wurde.

Die Deklaration des [Form.java]-Beans lautet daher wie folgt:


package forms;
 
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
 
@ManagedBean
@SessionScoped
public class Form {

Zeile 8 weist dem Bean einen Session-Gültigkeitsbereich zu.

2.7.4. Ausnahmebehandlung

Werfen wir noch einmal einen Blick auf die allgemeine Architektur einer JSF-Anwendung:

Was passiert, wenn ein Ereignisbehandler oder ein Modell eine Ausnahme aus der Geschäftsschicht abfängt – beispielsweise eine unerwartete Unterbrechung der Verbindung zur Datenbank?

  • Ereignisbehandler [2a] können jede aus der [Geschäfts-]Schicht stammende Ausnahme abfangen und einen Navigationsschlüssel an den Controller [Faces-Servlet] zurückgeben, der auf eine für die Ausnahme spezifische Fehlerseite verweist;
  • Für Modelle ist diese Lösung nicht praktikabel, da sich das System bei ihrer Aufrufung [3,4] in der Rendering-Phase einer bestimmten XHTML-Seite befindet und nicht mehr in der Auswahlphase. Wie kann man Seiten wechseln, während man sich in der Rendering-Phase einer davon befindet? Eine einfache Lösung, wenn auch nicht immer geeignet, besteht darin, die Ausnahme nicht zu behandeln, wodurch sie sich bis zum Servlet-Container ausbreitet, auf dem die Anwendung läuft. Der Container kann so konfiguriert werden, dass er eine bestimmte Seite anzeigt, wenn eine Ausnahme ihn erreicht. Diese Lösung ist immer praktikabel, und wir werden sie nun untersuchen.

2.7.4.1. Konfigurieren der Webanwendung für die Ausnahmebehandlung

Die Konfiguration einer Webanwendung für die Ausnahmebehandlung erfolgt in der Datei [web.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <context-param>
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
    <param-value>client</param-value>
  </context-param>  
  <context-param>
    <param-name>javax.faces.PROJECT_STAGE</param-name>
    <param-value>Development</param-value>
  </context-param>
  <context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
  </context-param> 
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>
      30
    </session-timeout>
  </session-config>
  <welcome-file-list>
    <welcome-file>faces/form1.xhtml</welcome-file>
  </welcome-file-list>
  <error-page>
    <error-code>500</error-code>
    <location>/faces/exception.xhtml</location>
  </error-page>
  <error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/faces/exception.xhtml</location>
  </error-page>
</web-app>

Die Zeilen 32–39 enthalten die Definition von zwei Fehlerseiten. Sie können so viele <error-page>-Tags verwenden, wie Sie benötigen. Das <location>-Tag gibt die Seite an, die im Falle eines Fehlers angezeigt werden soll. Der mit der Seite verknüpfte Fehlertyp kann auf zwei Arten definiert werden:

  • Mithilfe des <exception-type>-Tags, das den Java-Typ der behandelten Ausnahme definiert. Somit legt das <error-page>-Tag in den Zeilen 36–39 fest, dass der Servlet-Container, wenn er während der Anwendungsausführung eine Ausnahme vom Typ [java.lang.Exception] oder einen davon abgeleiteten Typ (Zeile 37) abfängt, die Seite [/faces/exception.xhtml] (Zeile 38) anzeigen muss. Durch die Verwendung des allgemeinsten Ausnahmetyps [java.lang.Exception] stellen wir hier sicher, dass alle Ausnahmen behandelt werden,
  • über das <error-code>-Tag (Zeile 33), das einen HTTP-Fehlercode definiert. Wenn beispielsweise ein Browser die URL [http://machine:port/contexte/P] anfordert und die Seite P im Anwendungskontext nicht existiert, greift die Anwendung nicht in die Antwort ein. Es ist der Servlet-Container, der diese Antwort generiert, indem er eine Standard-Fehlerseite sendet. Die erste Zeile der HTTP-Antwort enthält einen 404-Fehlercode, der angibt, dass die angeforderte Seite P nicht existiert. Möglicherweise möchten Sie eine Antwort generieren, die beispielsweise dem visuellen Styleguide der Anwendung folgt oder Links zur Behebung des Problems bereitstellt. In diesem Fall würden Sie ein <error-page>-Tag mit einem <error-code>404</error-code>-Tag verwenden.

Oben ist der HTTP-Fehlercode 500 der Code, der im Falle eines „Absturzes“ der Anwendung zurückgegeben wird. Dies ist der Code, der zurückgegeben würde, wenn eine Ausnahme bis zum Servlet-Container weitergeleitet würde. Die beiden <error-page>-Tags in den Zeilen 28–35 sind daher wahrscheinlich überflüssig. Wir haben beide aufgenommen, um die beiden Möglichkeiten zur Fehlerbehandlung zu veranschaulichen.

2.7.4.2. Simulieren der Ausnahme

Eine Ausnahme wird künstlich über den Link [Throw an exception] ausgelöst:

Wenn Sie auf den Link [Ausnahme auslösen] [1] klicken, wird Seite [2] angezeigt.

Im Code für die [formx.xhtml]-Seiten wird der Link [Ausnahme auslösen] wie folgt generiert:


<!-- liens -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
...
          <h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
        </h:panelGrid>

In Zeile 5 sehen wir, dass beim Klicken auf den Link die Methode [form].throwException ausgeführt wird. Sie lautet wie folgt:


  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

Hier wird eine Ausnahme vom Typ [java.lang.Exception] ausgelöst. Sie wird bis zum Servlet-Container weitergeleitet, der daraufhin die Seite [/faces/exception.xhtml] anzeigt.

2.7.4.3. Informationen zu einer Ausnahme

Wenn eine Ausnahme bis zum Servlet-Container weitergeleitet wird, zeigt der Container die entsprechende Fehlerseite an, indem er Informationen über die Ausnahme an diese übergibt. Diese Informationen werden als neue Attribute zur aktuell verarbeiteten Anfrage hinzugefügt. Die Anfrage des Browsers und die Antwort, die er erhalten wird, sind in Java-Objekten vom Typ [HttpServletRequest request] und [HttpServletResponse response] gekapselt. Diese Objekte stehen in allen Phasen der Verarbeitung der Browseranfrage zur Verfügung.

Nach dem Empfang der HTTP-Anfrage vom Browser kapselt der Servlet-Container diese in das Java-Objekt [HttpServletRequest request] ein und erstellt das Objekt [HttpServletResponse response], das zur Generierung der Antwort verwendet wird. Dieses Objekt enthält insbesondere den TCP/IP-Kanal, der für den HTTP-Antwortstrom verwendet werden soll. Alle an der Verarbeitung des Anfrageobjekts beteiligten Schichten t1, t2, ..., tn haben Zugriff auf diese beiden Objekte. Jede von ihnen kann auf die Elemente der ursprünglichen Anfrage zugreifen und die Antwort vorbereiten, indem sie das Antwortobjekt erweitert. Beispielsweise kann eine Lokalisierungsschicht die Spracheinstellung der Antwort mithilfe der Methode response.setLocale(Locale l) festlegen.

Die verschiedenen Ebenen können über das Request-Objekt Informationen untereinander austauschen. Dieses Objekt verfügt über ein Attribut-Dictionary, das bei der Erstellung leer ist und von den nachfolgenden Verarbeitungsebenen gefüllt werden kann. Diese Ebenen können die für die nächste Verarbeitungsebene erforderlichen Informationen in die Attribute des Request-Objekts einfügen. Es gibt zwei Methoden zur Verwaltung der Attribute des Request-Objekts:

  • void setAttribute(String s, Object o), das ein durch die Zeichenkette s identifiziertes Objekt o zu den Attributen hinzufügt,
  • Object getAttribute(String s), das das durch die Zeichenkette s identifizierte Attribut o abruft.

Wenn eine Ausnahme an den Servlet-Container weitergeleitet wird, setzt der Container die folgenden Attribute in der zu verarbeitenden Anfrage:

key
Wert
javax.servlet.error.status_code
der HTTP-Fehlercode, der an den Client zurückgegeben wird
javax.servlet.error.exception
der Java-Typ der Ausnahme zusammen mit der Fehlermeldung.
javax.servlet.error.request_uri
die URL, die zum Zeitpunkt des Auftretens der Ausnahme angefordert wurde
javax.servlet.error.servlet_name
das Servlet, das die Anfrage bearbeitete, als die Ausnahme auftrat

Wir werden diese Anforderungsattribute auf der Seite [exception.xhtml] verwenden, um sie anzuzeigen.

2.7.4.4. Die Fehlerseite [ exception.xhtml]

Ihr Inhalt lautet wie folgt:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
    </h:head>
    <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
      <h:form id="formulaire">
        <h3><h:outputText value="#{msg['exception.header']}"/></h3>
        <h:panelGrid columnClasses="col1,col2" columns="2" border="1">
          <h:outputText value="#{msg['exception.httpCode']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
          <h:outputText value="#{msg['exception.message']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
          <h:outputText value="#{msg['exception.requestUri']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
          <h:outputText value="#{msg['exception.servletName']}"/>
          <h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
        </h:panelGrid>
        <!-- links -->
        <h:panelGrid columns="6">
          <h:commandLink value="1" action="form1"/>
          <h:commandLink value="2" action="#{form.doAction2}"/>
          <h:commandLink value="3" action="form3"/>
          <h:commandLink value="4" action="#{form.doAction4}"/>
          <h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
        </h:panelGrid>
      </h:form>
    </h:body>
  </f:view>
</html>

2.7.4.4.1. Ausdrücke auf der Ausnahmeseite

In der Verarbeitungskette für Client-Anfragen ist die XHTML-Seite normalerweise das letzte Glied in der Kette:

Alle Elemente in der Kette sind Java-Klassen, einschließlich der XHTML-Seite. Diese Seite wird vom Servlet-Container in ein Servlet umgewandelt, d. h. in eine Standard-Java-Klasse. Genauer gesagt wird die XHTML-Seite in Java-Code umgewandelt, der innerhalb der folgenden Methode ausgeführt wird:


public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
 
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HTTPSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
... 
...code de la page XHTML
 

Ab Zeile 14 finden Sie den Java-Code, der der XHTML-Seite entspricht. Dieser Code enthält eine Reihe von Objekten, die durch die Methode _jspService in Zeile 1 oben initialisiert wurden:

  • Zeile 1: HttpServletRequest request: die aktuell verarbeitete Anfrage,
  • Zeile 1: HttpServletResponse response: die Antwort, die an den Client gesendet wird,
  • Zeile 7: ServletContext application: ein Objekt, das die Webanwendung selbst repräsentiert. Wie das request-Objekt kann auch das application-Objekt Attribute haben. Diese werden von allen Anfragen aller Clients gemeinsam genutzt. Es handelt sich in der Regel um schreibgeschützte Attribute,
  • Zeile 6: HTTPSession session: repräsentiert die Sitzung des Clients. Wie die Objekte request und application kann auch das Objekt session Attribute haben. Diese werden von allen Anfragen desselben Clients gemeinsam genutzt,
  • Zeile 9: JspWriter out: ein Schreibstrom zum Browser des Clients. Dieses Objekt ist nützlich für die Fehlersuche auf einer XHTML-Seite. Alles, was über out.println(text) geschrieben wird, wird im Browser des Clients angezeigt.

Wenn #{Ausdruck} in einer JSF-Seite geschrieben wird, kann Ausdruck der Schlüssel eines Attributs der oben genannten Request-, Session- oder Application-Objekte sein. Das entsprechende Attribut wird nacheinander in diesen drei Objekten gesucht. Somit wird #{Schlüssel} wie folgt ausgewertet:

  1. request.getAttribute(key)
  2. session.getAttribute(key)
  3. application.getAttribute(key)

Sobald ein Wert ungleich null erhalten wird, wird die Auswertung von #{key} beendet. Möglicherweise möchten Sie genauer sein, indem Sie den Kontext angeben, in dem nach dem Attribut gesucht werden soll:

  • #{requestScope['key']}, um das Attribut im Request-Objekt zu suchen,
  • #{sessionScope['key']}, um das Attribut im Session-Objekt zu suchen,
  • #{applicationScope['key']}, um im Anwendungsobjekt nach dem Attribut zu suchen.

Dies wurde auf der Seite [exception.xhtml] auf Seite 116 durchgeführt. Die verwendeten Attribute lauten wie folgt:

key
domain
value
javax.servlet.error.status_code
Anfrage
javax.servlet.error.exception
gleich
gleich
javax.servlet.error.request_uri
gleich
gleich
javax.servlet.error.servlet_name
gleich
gleich

Die verschiedenen Meldungen, die für die JSF-Seite [exception.xhtml] benötigt werden, wurden den bestehenden Meldungsdateien hinzugefügt:

[messages_fr.properties]


exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=URL demandée lors de l'erreur
exception.servletName=Nom de la servlet demandée lorsque l'erreur s'est produite

[messages_en.properties]


exception.header=The following error occurred
exception.httpCode=HTTP error code
exception.message=Exception message
exception.requestUri=URL requested when error occurred
exception.servletName=Servlet requested when error occurred

2.8. Beispiel mv-jsf2-06: Validierung und Konvertierung von Benutzereingaben

2.8.1. Die Anwendung

Die Anwendung zeigt ein Eingabeformular an. Nach der Validierung wird dasselbe Formular als Antwort zurückgegeben, zusammen mit etwaigen Fehlermeldungen, falls die Eingabe als fehlerhaft erkannt wurde.

2.8.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Das Projekt [mv-jsf2-06] basiert erneut auf einer einzigen Seite [index.html] [1] und deren Vorlage [Form.java] [2]. Es verwendet weiterhin Meldungen aus [messages.properties], jedoch nur auf Französisch [3]. Die Option zum Ändern der Sprache ist nicht verfügbar.

2.8.3. Die Anwendungsumgebung

Hier stellen wir den Inhalt der Dateien zur Verfügung, die die Anwendung konfigurieren, ohne dabei näher darauf einzugehen. Diese Dateien helfen dabei, das Folgende besser zu verstehen.

[ faces-config.xml]


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

Zeile 17 ist neu. Sie wird später erläutert.

Die Meldungsdatei [messages_fr.properties]


form.titre=Jsf - validations et conversions
saisie1.prompt=1-Nombre entier de type int
saisie2.prompt=2-Nombre entier de type int
saisie3.prompt=3-Nombre entier de type int
data.required=Vous devez entrer une donn\u00e9e
integer.required=Vous devez entrer un nombre entier
saisie4.prompt=4-Nombre entier de type int dans l'intervalle [1,10]
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
saisie5.prompt=5-Nombre r\u00e9el de type double
double.required=Vous devez entrer un nombre
saisie6.prompt=6-Nombre r\u00e9el>=0  de type double
saisie6.error=6-Vous devez entrer un nombre >=0
saisie7.prompt=7-Bool\u00e9en
saisie7.error=7-Vous devez entrer un bool\u00e9en
saisie8.prompt=8-Date au format jj/mm/aaaa
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
date.required=Vous devez entrer une date
saisie9.prompt=9-Cha\u00eene de 4 caract\u00e8res
saisie9.error=9-Vous devez entrer une cha\u00eene de 4 caract\u00e8res exactement
saisie9B.prompt=9B-Heure au format hh:mm
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
submit=Valider
cancel=Annuler
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du mod\u00e8le du formulaire
saisie10.prompt=10-Nombre entier de type int <1 ou >7
saisie10.incorrecte=10-Saisie n\u00b0 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
saisies11et12.incorrectes=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisies11et12.incorrectes_detail=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisie11.prompt=11-Nombre entier de type int
saisie12.prompt=12-Nombre entier de type int
error.sign="!"
error.sign_detail="!"

Das Stylesheet [styles.css] lautet wie folgt:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.col4{
   background-color: #ccffcc
}

.error{
   color: #ff0000
}
 
.saisie{
   background-color: #ffcccc;
   border-color: #000000;
   border-width: 5px;
   color: #cc0033;
   font-family: cursive;
   font-size: 16px
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}

2.8.4. Die Seite [index.xhtml] und ihre Vorlage [Form.java]

Die Seite [index.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['form.titre']}"/></h2>
    <h:form id="formulaire">
      <h:messages globalOnly="true" />
      <h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
        <!-- line 1 -->
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
        <!-- line 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>
        <!-- line 3 -->
        <h:outputText value="#{msg['saisie2.prompt']}" />
        <h:inputText id="saisie2" value="#{form.saisie2}"  styleClass="saisie"/>
        <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
        <h:outputText value="#{form.saisie2}"/>
        <!-- line 4 -->
        <h:outputText value="#{msg['saisie3.prompt']}" />
        <h:inputText id="saisie3" value="#{form.saisie3}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie3" styleClass="error"/>
        <h:outputText value="#{form.saisie3}"/>
        <!-- line 5 -->
        <h:outputText value="#{msg['saisie4.prompt']}" />
        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
        </h:inputText>
        <h:message for="saisie4" styleClass="error"/>
        <h:outputText value="#{form.saisie4}"/>
        <!-- line 6 -->
        ...
        <!-- line 7 -->
        ...
        <!-- line 8 -->
        ...
        <!-- line 9 -->
        ...
        <!-- line 10 -->
        ...
        <!-- line 11 -->
        ...
        <!-- line 12 -->
        ...
        <!-- line 13 -->
        ...
      </h:panelGrid>
      <!-- control buttons -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        <h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

Die wichtigste neue Funktion ist die Verwendung von Tags:

  • zur Anzeige von Fehlermeldungen <h:messages> (Zeile 14), <h:message> (Zeilen 24, 29, 34),
  • die Gültigkeitsbeschränkungen für Eingaben auferlegen <f:validateLongRange> (Zeile 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
  • die einen Konverter zwischen der Eingabe und ihrem Modell definieren, wie z. B. <f:convertDateTime>.

Die Vorlage für diese Seite ist die folgende Klasse [Form.java]:


package forms;
 
import com.corejsf.util.Messages;
import java.util.Date;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
 
@ManagedBean
@RequestScoped
public class Form {
 
public Form() {
}
// foreclosures
private Integer saisie1 = 0;
private Integer saisie2 = 0;
private Integer saisie3 = 0;
private Integer saisie4 = 0;
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
private Boolean saisie7 = true;
private Date saisie8 = new Date();
private String saisie9 = "";
private Integer saisie10 = 0;
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
 
// actions
public String submit() {
...
}
 
public String cancel() {
...
}
 
// validators
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// getters and setters
...
}

Neu ist hier, dass die Modellfelder nicht mehr nur vom Typ String sind, sondern verschiedene Typen haben.

2.8.5. Die verschiedenen Formulareingaben

Wir werden nun die verschiedenen Formulareingaben nacheinander betrachten.

2.8.5.1. Eingaben 1 bis 4: Eingabe einer ganzen Zahl

Die Seite [index.xhtml] stellt Eingabefeld 1 in folgender Form dar:


<!-- ligne 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>

Das Modell form.saisie1 ist in [Form.java] wie folgt definiert:


private Integer saisie1 = 0;

Wenn der Browser eine GET-Anfrage sendet, zeigt die Seite [index.xhtml], die mit der Vorlage [Form.java] verknüpft ist, Folgendes an:

  • Zeile 2 ergibt [1],
  • Zeile 3 ergibt [2],
  • Zeile 4 zeigt [3] an,
  • Zeile 5 zeigt [4] an.

Angenommen, die folgende Eingabe wird eingegeben und übermittelt:

Wir erhalten dann das folgende Ergebnis in dem von der Anwendung zurückgegebenen Formular:

  • in [1] die falsche Eingabe,
  • in [2] die entsprechende Fehlermeldung,
  • In [3] sehen wir, dass sich der Wert des `Integer`-Feldes `saisie1` des Modells nicht geändert hat.

Lassen Sie uns erklären, was passiert ist. Kehren wir dazu zum Verarbeitungszyklus einer JSF-Seite zurück:

Wir betrachten diesen Zyklus für die Komponente:


<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>

und deren Vorlage:


private Integer saisie1 = 0;
  • In [A] wird die Seite [index.xhtml], die während der GET-Anfrage des Browsers gesendet wurde, wiederhergestellt. In [A] entspricht die Seite genau dem, was der Benutzer erhalten hat. Die Komponente id="saisie1" kehrt zu ihrem Ausgangswert „0“ zurück,
  • in [B] erhalten die Seitenkomponenten die vom Browser übermittelten Werte. In [B] entspricht die Seite der Eingabe und Validierung durch den Benutzer. Die Komponente id="saisie1" erhält den übermittelten Wert „x“,
  • in [C] werden, sofern die Seite explizite Validatoren und Konverter enthält, diese ausgeführt. Implizite Konverter werden ebenfalls ausgeführt, wenn der Typ des mit der Komponente verknüpften Feldes nicht vom Typ String ist. Dies ist hier der Fall, da das Feld form.saisie1 vom Typ Integer ist. JSF versucht, den Wert „x“ der Komponente id="saisie1" in einen Integer-Typ zu konvertieren. Dies führt zu einem Fehler, der den Verarbeitungszyklus [A-F] unterbricht. Dieser Fehler wird der Komponente id="saisie1" zugeordnet. Über [D2] gelangen wir dann direkt zur Phase der Antwortdarstellung. Es wird dieselbe Seite [index.xhtml] zurückgegeben,
  • Phase [D] tritt nur ein, wenn alle Komponenten auf einer Seite die Konvertierungs-/Validierungsphase bestanden haben. In dieser Phase wird der Wert der Komponente id="saisie1" ihrem Modell form.saisie1 zugewiesen.

Wenn Phase [C] fehlschlägt, wird die Seite erneut angezeigt und der folgende Code erneut ausgeführt:


<!-- ligne 2 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
        <h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>

Die unter [2] angezeigte Meldung stammt aus Zeile 4 der Datei [index.xhtml]. Der Tag <h:message for="idComposant"/> zeigt die Fehlermeldung an, die der durch das for-Attribut angegebenen Komponente zugeordnet ist, falls ein Fehler auftritt. Die unter [2] angezeigte Meldung ist eine Standardmeldung und befindet sich in der Datei [javax/faces/Messages.properties] im Archiv [jsf-api.jar]:

In [2] sehen wir, dass die Meldungsdatei in mehreren Varianten vorliegt. Sehen wir uns den Inhalt von [Messages_fr.properties] an:

...
# ==============================================================================
# Component Errors
# ==============================================================================
javax.faces.component.UIInput.CONVERSION={0} : une erreur de conversion est survenue.
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.
javax.faces.component.UIInput.UPDATE={0} : une erreur est survenue lors du traitement des informations que vous avez soumises. 
javax.faces.component.UISelectOne.INVALID={0} : erreur de validation. La valeur est incorrecte.
javax.faces.component.UISelectMany.INVALID={0} : erreur de validation. La valeur est incorrecte.

# ==============================================================================
# Converter Errors
# ==============================================================================
...
javax.faces.converter.FloatConverter.FLOAT={2} : «{0 doit être un nombre composé dun ou de plusieurs chiffres.
javax.faces.converter.FloatConverter.FLOAT_detail={2} : «{0 doit être un nombre compris entre 1.4E-45 et 3.4028235E38. Exemple : {1}
javax.faces.converter.IntegerConverter.INTEGER={2} : «{0 doit être un nombre composé dun ou de plusieurs chiffres.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0 doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}
...


# ==============================================================================
# Validator Errors
# ==============================================================================
javax.faces.validator.DoubleRangeValidator.MAXIMUM={1} : erreur de validation. La valeur est supérieure à la valeur maximale autorisée, "{0}".
javax.faces.validator.DoubleRangeValidator.MINIMUM={1} : erreur de validation. La valeur est inférieure à la valeur minimale autorisée, "{0}".
javax.faces.validator.DoubleRangeValidator.NOT_IN_RANGE={2} : erreur de validation. Lattribut spécifié nest pas compris entre les valeurs attendues {0} et {1}.
javax.faces.validator.DoubleRangeValidator.TYPE={0} : erreur de validation. La valeur nest pas du type correct.
...

Die Datei enthält Meldungen, die in Kategorien unterteilt sind:

  • Fehler an einer Komponente, Zeile 3,
  • Konvertierungsfehler zwischen einer Komponente und ihrem Modell, Zeile 12
  • Validierungsfehler, wenn Validatoren auf der Seite vorhanden sind, Zeile 23.

Der Fehler, der bei der Komponente id="saisie1" aufgetreten ist, ist ein Konvertierungsfehler vom Typ String zum Typ Integer. Die zugehörige Fehlermeldung ist die in Zeile 18 der Meldungsdatei.

javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}

Die angezeigte Fehlermeldung ist nachfolgend wiedergegeben:

In der Meldung ist Folgendes zu sehen:

  • Der Parameter {2} wurde durch die ID der Komponente ersetzt, bei der der Konvertierungsfehler aufgetreten ist,
  • der Parameter {0} wurde durch den unter [1] für die Komponente eingegebenen Wert ersetzt,
  • der Parameter {1} wurde durch die Zahl 9346 ersetzt.

Die meisten komponentenbezogenen Meldungen gibt es in zwei Versionen: einer Zusammenfassung und einer detaillierten Version. Dies ist bei den Zeilen 16–18 der Fall:

javax.faces.converter.IntegerConverter.INTEGER={2} : «{0}» doit être un nombre composé d’un ou de plusieurs chiffres.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}

Die Meldung mit dem Schlüssel „_detail“ (Zeile 2) ist die sogenannte Detailmeldung. Die andere ist die sogenannte Zusammenfassungsmeldung. Das <h:message>-Tag zeigt standardmäßig die Detailmeldung an. Dieses Verhalten kann mithilfe der Attribute „showSummary“ und „showDetail“ geändert werden. Genau das geschieht bei der Komponente mit der ID „saisie2“:


        <!-- ligne 3 -->
        <h:outputText value="#{msg['saisie2.prompt']}" />
        <h:inputText id="saisie2" value="#{form.saisie2}"  styleClass="saisie"/>
        <h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>

Zeile 2: Die Komponente „saisie2“ ist mit dem folgenden Feld „form.saisie2“ verknüpft:


  private Integer saisie2 = 0;

Das Ergebnis lautet wie folgt:

  • in [1] die detaillierte Meldung; in [2] die Zusammenfassung der Meldung.

Das <h:messages>-Tag zeigt alle zusammengefassten Fehlermeldungen aller Komponenten sowie Fehlermeldungen, die keiner bestimmten Komponente zugeordnet sind, in Form einer Liste an. Auch hier können Attribute dieses Standardverhalten ändern:

  • showDetail: true / false zum Aktivieren oder Deaktivieren detaillierter Meldungen,
  • showSummary: true / false, um Zusammenfassungsmeldungen anzuzeigen oder auszublenden,
  • globalOnly: true / false, um festzulegen, ob nur Fehlermeldungen angezeigt werden sollen, die nicht mit Komponenten verknüpft sind. Eine solche Meldung könnte beispielsweise vom Entwickler erstellt worden sein.

Die mit einer Konvertierung verbundene Fehlermeldung kann auf verschiedene Weise geändert werden. Zunächst können Sie die Anwendung anweisen, eine andere Meldungsdatei zu verwenden. Diese Änderung wird in [faces-config.xml] vorgenommen:


<faces-config ...">
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
...
</faces-config>

Die Zeilen 3–8 definieren eine Meldungsdatei, aber dies ist nicht diejenige, die von den Tags <h:message> und <h:messages> verwendet wird. Sie müssen das <message-bundle>-Tag in Zeile 9 verwenden, um sie zu definieren. Die Zeile 9 teilt den <h:message(s)>-Tags mit, dass die Datei [messages.properties] vor der Datei [javax.faces.Messages.properties] durchsucht werden soll. Wenn wir also die folgenden Zeilen zur Datei [messages_fr.properties] hinzufügen:


# conversions
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e

Der für die Komponenten input1 und input2 zurückgegebene Fehler lautet:

Image

Eine weitere Möglichkeit, die Konvertierungsfehlermeldung zu ändern, besteht darin, das Attribut „converterMessage“ der Komponente zu verwenden, wie unten für die Komponente „saisie3“ gezeigt:


        <!-- ligne 4 -->
        <h:outputText value="#{msg['saisie3.prompt']}" />
        <h:inputText id="saisie3" value="#{form.saisie3}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>

Die Komponente „saisie3“ ist mit dem folgenden Feld „form.saisie3“ verknüpft:


  private Integer saisie3 = 0;
  • In Zeile 3 legt das Attribut converterMessage explizit die Meldung fest, die im Falle eines Konvertierungsfehlers angezeigt werden soll.
  • Zeile 3: Das Attribut required="true" gibt an, dass die Eingabe erforderlich ist. Das Feld darf nicht leer bleiben. Ein Feld gilt als leer, wenn es keine Zeichen enthält oder wenn es eine Folge von Leerzeichen enthält. Auch hier gibt es eine Standardmeldung in [javax.faces.Messages.properties]:
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.

Mit dem Attribut „requiredMessage“ können Sie diese Standardmeldung ersetzen. Wenn die Datei [messages.properties] die folgenden Meldungen enthält:


...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
 

Das folgende Ergebnis kann erzielt werden:

oder dieses:

Es reicht nicht immer aus, zu prüfen, ob eine Eingabe tatsächlich eine Ganzzahl ist. Manchmal muss man sicherstellen, dass die eingegebene Zahl in einem bestimmten Bereich liegt. In solchen Fällen wird ein Validator verwendet. Beispiel 4 liefert ein Beispiel dafür. Der Code in [index.xhtml] lautet wie folgt:


        <!-- ligne 5 -->
        <h:outputText value="#{msg['saisie4.prompt']}" />
        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
        </h:inputText>
        <h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>

Zeile 3: Die Komponente „saisie4“ ist an das folgende Modell „form.saisie4“ gebunden:


  private Integer saisie4 = 0;

Zeilen 3–5: Das <h:inputText>-Tag enthält ein untergeordnetes <f:validateLongRange>-Tag, das zwei optionale Attribute akzeptiert: minimum und maximum. Dieses Tag, auch als Validator bezeichnet, ermöglicht es Ihnen, eine Einschränkung für den Eingabewert festzulegen: Er muss nicht nur eine Ganzzahl sein, sondern eine Ganzzahl im Bereich [minimum, maximum], wenn sowohl das minimum- als auch das maximum-Attribut vorhanden sind; größer oder gleich minimum, wenn nur das minimum-Attribut vorhanden ist; und kleiner oder gleich maximum, wenn nur das maximum-Attribut vorhanden ist. Der Validator <f:validateLongRange> verfügt über Standardfehlermeldungen in [javax.faces.Messages.properties]:

1
2
3
javax.faces.validator.LongRangeValidator.MINIMUM={1} : erreur de validation. La valeur est inférieure à la valeur minimale autorisée, "{0}".
javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2} : erreur de validation. L’attribut spécifié n’est pas compris entre les valeurs attendues {0} et {1}.
javax.faces.validator.LongRangeValidator.TYPE={0} : erreur de validation. La valeur n’est pas du type correct.

Auch hier ist es möglich, diese Meldungen durch andere zu ersetzen. Es gibt ein validatorMessage-Attribut, mit dem Sie eine spezifische Meldung für die Komponente definieren können. Mit dem folgenden JSF-Code:


        <h:inputText id="saisie4" value="#{form.saisie4}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
          <f:validateLongRange minimum="1" maximum="10" />
</h:inputText>

und die folgende Meldung in [messages.properties]:


saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]

Es ergibt sich folgendes Ergebnis:

Image

2.8.5.2. Eingaben 5 und 6: Eingabe einer reellen Zahl

Die Eingabe von reellen Zahlen folgt ähnlichen Regeln wie die Eingabe von ganzen Zahlen. Der XHTML-Code für die Einträge 5 und 6 lautet wie folgt:


<!-- ligne 6 -->
        <h:outputText value="#{msg['saisie5.prompt']}" />
        <h:inputText id="saisie5" value="#{form.saisie5}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
        <h:message for="saisie5" styleClass="error"/>
        <h:outputText value="#{form.saisie5}"/>
        <!-- ligne 7 -->
        <h:outputText value="#{msg['saisie6.prompt']}"/>
        <h:inputText id="saisie6" value="#{form.saisie6}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}" validatorMessage="#{msg['saisie6.error']}">
          <f:validateDoubleRange minimum="0.0"/>
        </h:inputText>
        <h:message for="saisie6" styleClass="error"/>
        <h:outputText value="#{form.saisie6}"/>

Die Elemente des Modells [Form.java], die sich auf die Komponenten saisie5 und saisie6 beziehen:


  private Double saisie5 = 0.0;
  private Double saisie6 = 0.0;

Die Fehlermeldungen zu den Konvertern und Validatoren für die Komponenten saisie5 und saisie6 in [messages.properties]:


double.required=Vous devez entrer un nombre
saisie6.error=6-Vous devez entrer un nombre >=0

Hier ist ein Ausführungsbeispiel:

Image

2.8.5.3. Eingabe 7: Eingabe eines Booleschen Werts

Die Eingabe eines Booleschen Werts sollte normalerweise über ein Kontrollkästchen erfolgen. Wenn dies über ein Eingabefeld geschieht, wird die Zeichenfolge „true“ in den Booleschen Wert „true“ umgewandelt, und jede andere Zeichenfolge wird in den Booleschen Wert „false“ umgewandelt.

Der XHTML-Code für das Beispiel:


<!-- ligne 8 -->
        <h:outputText value="#{msg['saisie7.prompt']}"/>
        <h:inputText id="saisie7" value="#{form.saisie7}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
        <h:message for="saisie7" styleClass="error"/>
        <h:outputText value="#{form.saisie7}"/>

Die Vorlage für die Komponente „saisie7“:


  private Boolean saisie7 = true;

Hier ist ein Beispiel für eine Eingabe und die entsprechende Reaktion:

In [1] der eingegebene Wert. Nach der Konvertierung wird diese Zeichenfolge „x“ zum booleschen Wert „false“. Dies wird in [2] angezeigt. Der Wert [3] des Modells hat sich nicht geändert. Er ändert sich erst, wenn alle Konvertierungen und Validierungen auf der Seite erfolgreich waren. Dies war in diesem Beispiel nicht der Fall.

2.8.5.4. Eingabe 8: Eingabe eines Datums

In diesem Beispiel wird ein Datum mithilfe des folgenden XHTML-Codes eingegeben:


<!-- ligne 9 -->
        <h:outputText value="#{msg['saisie8.prompt']}"/>
        <h:inputText id="saisie8" value="#{form.saisie8}"  styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
          <f:convertDateTime pattern="dd/MM/yyyy"/>
        </h:inputText>
        <h:message for="saisie8" styleClass="error"/>
        <h:outputText value="#{form.saisie8}">
          <f:convertDateTime pattern="dd/MM/yyyy"/>
        </h:outputText>

Die Komponente „saisie8“ in Zeile 3 verwendet einen Konverter zwischen „java.lang.String“ und „java.util.Date“. Die der Komponente „saisie8“ zugeordnete Vorlage „form.saisie8“ lautet wie folgt:


  private Date saisie8 = new Date();

Die in den Zeilen 7–9 definierte Komponente verwendet ebenfalls einen Konverter, jedoch nur in der Richtung java.util.Date --> java.lang.String.

Der Konverter <f:convertDateTime> unterstützt verschiedene Attribute, darunter das Attribut „pattern“, das das Format der in ein Datum zu konvertierenden Zeichenfolge oder das Format angibt, in dem ein Datum angezeigt werden soll.

Wenn die Seite [index.xhtml] zum ersten Mal aufgerufen wird, wird die obige Zeile 8 wie folgt angezeigt:

Die Felder [1] und [2] zeigen beide den Wert des Modells form.saisie8 an:


  private Date saisie8 = new Date();

wobei saisie8 auf das aktuelle Datum gesetzt ist. Der in beiden Fällen zur Anzeige des Datums verwendete Konverter lautet wie folgt:


            <f:convertDateTime pattern="dd/MM/yyyy"/>

wobei dd (Tag) die Tageszahl, MM (Monat) die Monatszahl und yyyy (Jahr) das Jahr bezeichnet. In [1] wird der Konverter für die umgekehrte Konvertierung java.lang.String --> java.util.Date verwendet. Das eingegebene Datum muss daher dem Format „dd/MM/yyyy“ entsprechen, um gültig zu sein.

In [javax.faces.Messages.properties] sind Standardmeldungen für ungültige Datumsangaben enthalten:

javax.faces.converter.DateTimeConverter.DATE={2} : «{0}» n’a pas pu être interprété en tant que date.
javax.faces.converter.DateTimeConverter.DATE_detail={2} : «{0}» n’a pas pu être interprété en tant que date. Exemple : {1} 

die durch Ihre eigenen Meldungen ersetzt werden können. Zum Beispiel:


<h:inputText id="saisie8" value="#{form.saisie8}"  styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
  <f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>

Die im Falle eines Konvertierungsfehlers angezeigte Meldung ist die folgende Schlüsselmeldung: saisie8.error:


saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa

Hier ein Beispiel:

Image

2.8.5.5. Eingabe 9: Eingabe einer Zeichenfolge mit begrenzter Länge

Beispiel 9 zeigt, wie sichergestellt werden kann, dass eine eingegebene Zeichenkette eine Zeichenanzahl innerhalb eines bestimmten Bereichs hat:


<!-- ligne 10 -->
        <h:outputText value="#{msg['saisie9.prompt']}"/>
        <h:inputText id="saisie9" value="#{form.saisie9}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9.error']}">
          <f:validateLength minimum="4" maximum="4"/>
        </h:inputText>
        <h:message for="saisie9" styleClass="error"/>
        <h:outputText value="#{form.saisie9}"/>

Zeile 4: Der Validator <f:validateLength minimum="4" maximum="4"/> verlangt, dass die eingegebene Zeichenfolge genau 4 Zeichen lang ist. Sie können nur eines der Attribute verwenden: „minimum“ für eine Mindestanzahl an Zeichen oder „maximum“ für eine Höchstanzahl.

Die Vorlage „form.saisie9“ für die Komponente „saisie9“ in Zeile 3 lautet wie folgt:


  private String saisie9 = "";

Für diese Art der Validierung gibt es Standardfehlermeldungen:

javax.faces.validator.LengthValidator.MAXIMUM={1} : erreur de validation. La longueur est supérieure à la valeur maximale autorisée, "{0}".
javax.faces.validator.LengthValidator.MINIMUM={1} : erreur de validation. La longueur est inférieure à la valeur minimale autorisée, "{0}".

die mithilfe des Attributs validatorMessage ersetzt werden können, wie in Zeile 3 oben gezeigt. Die Meldung für den Schlüssel „saisie9.error“ lautet wie folgt:


saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement

Hier ist ein Ausführungsbeispiel:

Image

2.8.5.6. Eingabe 9B: Eingabe einer Zeichenfolge, die einem Muster entsprechen muss

Eintrag 9B zeigt, wie sichergestellt werden kann, dass eine eingegebene Zeichenfolge eine bestimmte Anzahl von Zeichen innerhalb eines bestimmten Bereichs haben muss:


<!-- ligne 10B -->
        <h:outputText value="#{msg['saisie9B.prompt']}"/>
        <h:inputText id="saisie9B" value="#{form.saisie9B}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9B.error']}">
          <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/>
        </h:inputText>
        <h:message for="saisie9B" styleClass="error"/>
        <h:outputText value="#{form.saisie9B}"/>

Zeile 4: Der Validator <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> verlangt, dass die eingegebene Zeichenfolge einem regulären Ausdruck entspricht, in diesem Fall: einer Folge von 0 oder mehr Leerzeichen, 2 Ziffern, dem Doppelpunkt (:), 2 Ziffern und einer Folge von 0 oder mehr Leerzeichen.

Das Muster form.saisie9B der Komponente saisie9B in Zeile 3 lautet wie folgt:


private String saisie9B;

Für diese Art der Validierung gibt es Standardfehlermeldungen:

1
2
3
4
5
6
javax.faces.validator.RegexValidator.PATTERN_NOT_SET=Le modèle d’expression régulière doit être défini.
javax.faces.validator.RegexValidator.PATTERN_NOT_SET_detail=La valeur définie du modèle d’expression régulière ne peut pas être vide.
javax.faces.validator.RegexValidator.NOT_MATCHED=Discordance du modèle d’expression régulière.
javax.faces.validator.RegexValidator.NOT_MATCHED_detail=Discordance du modèle d’expression régulière «{0}».
javax.faces.validator.RegexValidator.MATCH_EXCEPTION=Erreur dans l’expression régulière.
javax.faces.validator.RegexValidator.MATCH_EXCEPTION_detail=Erreur dans l’expression régulière,  «{0}»

die durch Verwendung des Attributs validatorMessage ersetzt werden kann, wie in Zeile 3 oben gezeigt. Die Meldung für den Schlüssel saisie9.error lautet wie folgt:


saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm

Hier ist ein Ausführungsbeispiel:

Image

2.8.5.7. Eintrag 10: Schreiben Sie eine spezifische Validierungsmethode

Zusammenfassend lässt sich sagen: Mit JSF können Sie unter den eingegebenen Werten die Gültigkeit von Zahlen (Ganzzahlen, Gleitkommazahlen), Datumsangaben und die Länge von Zeichenfolgen überprüfen sowie feststellen, ob eine Eingabe einem regulären Ausdruck entspricht. JSF ermöglicht es Ihnen, eigene Validatoren und Konverter zu den vorhandenen hinzuzufügen. Dieses Thema wird hier nicht behandelt, aber Sie können [ref2] für weitere Details heranziehen.

Hier stellen wir eine weitere Methode vor: eine, bei der eingegebene Daten mithilfe einer Methode aus dem Formularmodell validiert werden. Hier ist ein Beispiel:


<!-- ligne 11 -->
        <h:outputText value="#{msg['saisie10.prompt']}"/>
        <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
        <h:message for="saisie10" styleClass="error"/>
        <h:outputText value="#{form.saisie10}"/>

Die Vorlage form.saisie10, die der Komponente saisie10 in Zeile 3 zugeordnet ist, lautet wie folgt:


  private Integer saisie10 = 0;

Wir möchten, dass die eingegebene Zahl kleiner als 1 oder größer als 7 ist. Mit den Standard-Validatoren von JSF können wir dies nicht überprüfen. Daher schreiben wir eine eigene Validierungsmethode für die Komponente „saisie10“. Diese legen wir über das Attribut „validator“ der zu validierenden Komponente fest:


          <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>

Die Komponente „saisie10“ wird durch die Methode „form.validateSaisie10“ validiert. Die Methode lautet wie folgt:


  public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
    int saisie = (Integer) value;
    if (!(saisie < 1 || saisie > 7)) {
      FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      throw new ValidatorException(message);
    }
}

Die Signatur einer Validierungsmethode muss der in Zeile 1 gezeigten entsprechen:

  • FacesContext context: der Ausführungskontext der Seite – bietet Zugriff auf verschiedene Informationen, darunter die Objekte HttpServletRequest (Anfrage) und HttpServletResponse (Antwort),
  • UIComponent component: die zu validierende Komponente. Das <h:inputText>-Tag wird durch eine von UIComponent abgeleitete UIInput-Komponente dargestellt. Hier wird diese UIInput-Komponente als zweiter Parameter übergeben,
  • Object value: der einzugebende Wert, der überprüft werden soll, konvertiert in den Typ des Modells. Es ist wichtig zu verstehen, dass die Validierungsmethode nicht ausgeführt wird, wenn die Konvertierung von String in den Modelltyp fehlschlägt. Wenn die Methode validateSaisie10 aufgerufen wird, bedeutet dies, dass die Konvertierung von String in Integer erfolgreich war. Der dritte Parameter ist daher vom Typ Integer.
  • Zeile 2: Der eingegebene Wert wird in den Typ int konvertiert,
  • Zeile 3: Wir prüfen, ob der eingegebene Wert <1 oder >7 ist. Ist dies der Fall, ist die Validierung abgeschlossen. Ist dies nicht der Fall, muss der Validator den Fehler melden, indem er eine ValidatorException auslöst.

Die Klasse „ValidatorException“ verfügt über zwei Konstruktoren:

  • Der Konstruktor [1] nimmt eine Fehlermeldung vom Typ FacesMessage als Parameter entgegen. Dieser Nachrichtentyp wird von den Tags <h:messages> und <h:message> angezeigt,
  • Der Konstruktor [2] ermöglicht es Ihnen zudem, die Ursache des Fehlers (ein „Throwable“ oder eine davon abgeleitete Klasse) zu kapseln.

Wir müssen eine Nachricht vom Typ FacesMessage erstellen. Diese Klasse verfügt über mehrere Konstruktoren:

Der Konstruktor [1] definiert die Eigenschaften eines FacesMessage-Objekts:

  • FacesMessage.Severity severity: ein Schweregrad aus der folgenden Aufzählung: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
  • String summary: die Kurzfassung der Fehlermeldung – wird von den Tags <h:message showSummary="true"> und <h:messages> angezeigt,
  • String detail: die detaillierte Version der Fehlermeldung – wird von den Tags <h:message> und <h:messages showDetail="true"> angezeigt.

Es kann jeder der Konstruktoren verwendet werden; fehlende Parameter können später mithilfe von Set-Methoden gesetzt werden.

Der Konstruktor [1] erlaubt es nicht, eine Meldung aus einer internationalisierten Meldungsdatei anzugeben. Das ist natürlich schade. David Geary und Cay Horstmann [ref2] beheben diesen Mangel in ihrem Buch „Core JavaServer Faces“ mit der Utility-Klasse com.corejsf.util.Messages. Dies ist die Klasse, die in Zeile 4 des Java-Codes zum Erstellen der Fehlermeldung verwendet wird. Sie enthält nur statische Methoden, darunter die in Zeile 4 verwendete Methode getMessage:


   public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)

Die Methode getMessage akzeptiert drei Parameter:

  • String bundleName: der Name einer Meldungsdatei ohne die Endung .properties, aber mit dem Paketnamen. Hier könnte unser erster Parameter `messages` lauten, um auf die Datei `[messages.properties]` zu verweisen. Bevor die durch den ersten Parameter angegebene Datei verwendet wird, versucht `getMessage`, die Meldungsdatei der Anwendung zu verwenden, sofern eine vorhanden ist. Wenn wir also in `[faces-config.xml]` eine Meldungsdatei mit dem Tag deklariert haben:

  <application>
...
    <message-bundle>messages</message-bundle>
</application>

können wir null als ersten Parameter an die Methode getMessage übergeben. Genau das wurde hier getan (siehe [web.xml], Seite 120),

  • String resourceId: der Schlüssel der Nachricht, die aus der Nachrichtendatei abgerufen werden soll. Wir haben gesehen, dass eine Nachricht sowohl eine Zusammenfassungsversion als auch eine Detailversion haben kann. resourceId ist die Kennung für die Zusammenfassungsversion. Die Detailversion wird automatisch unter Verwendung des Schlüssels resourceId_detail abgerufen. Somit haben wir in [messages.properties] zwei Nachrichten für den Fehler bei Eintrag Nr. 10:

saisie10.incorrecte=10-Saisie  10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7

Die von der Methode Messages.getMessage erzeugte Meldung vom Typ FacesMessage enthält sowohl die Zusammenfassung als auch die Detailansicht, sofern diese gefunden wurden. Beide Versionen müssen vorhanden sein; andernfalls wird eine [NullPointerException] ausgelöst,

  • Object[] params: die tatsächlichen Parameter der Meldung, falls sie formale Parameter {0}, {1}, ... hat. Diese formalen Parameter werden durch die Elemente des Arrays params ersetzt.

Kehren wir zum Code für die Validierungsmethode der Komponente „saisie10“ zurück:


  public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
    int saisie = (Integer) value;
    if (!(saisie < 1 || saisie > 7)) {
      FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      throw new ValidatorException(message);
    }
}
  • In [4] wird die FacesMessage mithilfe der statischen Methode Messages.getMessage erstellt,
  • in [5] wird der Schweregrad der Meldung festgelegt,
  • in [6] wird eine ValidatorException mit der zuvor erstellten Meldung ausgelöst. Die Validierungsmethode wurde durch den folgenden XHTML-Code aufgerufen:

<!-- ligne 11 -->
        <h:outputText value="#{msg['saisie10.prompt']}"/>
        <h:inputText id="saisie10" value="#{form.saisie10}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
        <h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>

Zeile 3: Die Validierungsmethode wird für die Komponente mit der ID „saisie10“ ausgeführt. Daher wird die von der Methode validateSaisie10 generierte Fehlermeldung dieser Komponente zugeordnet und in Zeile 4 angezeigt (Attribut for="saisie10"). Die detaillierte Version wird standardmäßig durch das <h:message>-Tag angezeigt.

Hier ein Ausführungsbeispiel:

Image

2.8.5.8. Einträge 11 und 12: Validierung einer Gruppe von Komponenten

Bisher haben die Validierungsmethoden, die wir kennengelernt haben, nur eine einzelne Komponente validiert. Was ist, wenn die gewünschte Validierung mehrere Komponenten umfasst? Das werden wir uns nun ansehen. Im Formular:

Image

sollen die Einträge 11 und 12 zwei ganze Zahlen sein, deren Summe 10 ergibt.

Der JSF-Code sieht wie folgt aus:


<!-- line 12 -->
        <h:outputText value="#{msg['saisie11.prompt']}"/>
        <h:inputText id="saisie11" value="#{form.saisie11}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie11" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie11}"/>
        <!-- line 13 -->
        <h:outputText value="#{msg['saisie12.prompt']}"/>
        <h:inputText id="saisie12" value="#{form.saisie12}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie12" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie12}"/>

und das zugehörige Modell:


  private Integer saisie11 = 0;
  private Integer saisie12 = 0;
  private String errorSaisie11 = "";
private String errorSaisie12 = "";

In Zeile 3 des JSF-Codes verwenden wir die bereits vorgestellten Techniken, um zu überprüfen, ob der für die Komponente saisie11 eingegebene Wert tatsächlich eine Ganzzahl ist. Dasselbe gilt in Zeile 11 für die Komponente saisie12. Um zu überprüfen, ob saisie11 + saisie12 = 10 ist, könnten wir einen speziellen Validator erstellen. Dies ist die bevorzugte Lösung. Auch hier verweisen wir auf [ref2], um mehr zu erfahren. Hier verfolgen wir einen anderen Ansatz.

Die Seite [index.xhtml] wird durch eine Schaltfläche [Validate] validiert, deren JSF-Code wie folgt lautet:


<!-- boutons de commande -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        ...
      </h:panelGrid>

wobei die Nachricht msg['submit'] wie folgt lautet:


submit=Valider

Wie in Zeile 3 zu sehen ist, wird die Methode form.submit ausgeführt, um den Klick auf die Schaltfläche [Validate] zu verarbeiten. Sie lautet wie folgt:


  // actions
  public String submit() {
    // latest validations
    validateForm();
    // we return the same form
    return null;
  }
 
  // global validations
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
...
}

Es ist wichtig zu verstehen, dass bei der Ausführung der submit-Methode:

  • alle Formularvalidatoren und Konverter bereits ausgeführt wurden und erfolgreich waren,
  • die Felder im [Form.java]-Modell die vom Client gesendeten Werte erhalten haben.

Schauen wir uns den Verarbeitungszyklus eines JSF-POST-Aufrufs noch einmal an:

Die submit-Methode ist ein Ereignisbehandler. Sie verarbeitet das Klick-Ereignis auf die Schaltfläche [Validate]. Wie alle Ereignisbehandler wird sie in Phase [E] ausgeführt, nachdem alle Validatoren und Konverter ausgeführt wurden und erfolgreich waren [C] und das Modell mit den übermittelten Werten aktualisiert wurde [D]. Es besteht also hier keine Notwendigkeit mehr, Ausnahmen vom Typ [ValidatorException] auszulösen, wie wir es zuvor getan haben. Wir senden das Formular einfach mit Fehlermeldungen erneut ab:

In [1] werden wir den Benutzer warnen, und in [2] und [3] werden wir eine Fehleranzeige anzeigen. Im JSF-Code wird die Meldung [1] wie folgt generiert:


<h:form id="formulaire">
      <h:messages globalOnly="true" />
      <h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
        <!-- ligne 1 -->
        ...

In Zeile 2 zeigt das <h:messages>-Tag standardmäßig die zusammengefasste Version der Fehlermeldungen für alle ungültigen Eingaben in Formularkomponenten sowie alle Fehlermeldungen an, die nicht mit Komponenten verknüpft sind. Das Attribut globalOnly="true" beschränkt die Anzeige auf Letztere.

Die Meldungen [2] und [3] werden mithilfe einfacher <h:outputText>-Tags angezeigt:


<!-- line 12 -->
        <h:outputText value="#{msg['saisie11.prompt']}"/>
        <h:inputText id="saisie11" value="#{form.saisie11}"  styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
        <h:panelGroup>
          <h:message for="saisie11" styleClass="error"/>
          <h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
        </h:panelGroup>
        <h:outputText value="#{form.saisie11}"/>
        <!-- line 13 -->
        ...
          <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
        ...

Zeilen 4–7: Die Komponente „saisie11“ kann zwei Fehlermeldungen ausgeben:

  • eine, die auf eine ungültige Umwandlung oder fehlende Daten hinweist. Diese von JSF selbst generierte Meldung wird in einem FacesMessage-Typ enthalten sein und durch das <h:message>-Tag in Zeile 5 angezeigt,
  • die andere, die wir generieren, wenn input11 + input12 nicht gleich 10 ist. Sie wird in Zeile 6 angezeigt. Die Fehlermeldung ist in der Vorlage form.errorSaisie11 enthalten.

Die beiden Meldungen beziehen sich auf Fehler, die nicht gleichzeitig auftreten können. Die Überprüfung input11 + input12 = 10 wird in der Methode submit durchgeführt, die nur ausgeführt wird, wenn im Formular keine Fehler mehr vorhanden sind. Bei der Ausführung ist die Komponente input11 bereits validiert und ihr Modell form.input11 hat ihren Wert erhalten. Die Meldung in Zeile 5 wird nicht mehr angezeigt. Umgekehrt gilt: Wenn die Meldung in Zeile 5 angezeigt wird, ist mindestens ein Fehler im Formular vorhanden und die submit-Methode wird nicht ausgeführt. Die Meldung in Zeile 6 wird nicht angezeigt. Um sicherzustellen, dass die beiden möglichen Fehlermeldungen in derselben Spalte der Tabelle stehen, wurden sie innerhalb eines &lt;**h:panelGroup**&gt;-Tags gruppiert (Zeilen 4 und 7).

Die „submit“-Methode lautet wie folgt:


  // actions
  public String submit() {
    // latest validations
    validateForm();
    // we return the same form
    return null;
  }
 
  // global validations
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
      // global msg
      FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      FacesContext context = FacesContext.getCurrentInstance();
      context.addMessage(null, message);
      // field-related msg
      message = Messages.getMessage(null, "error.sign", null);
      setErrorSaisie11(message.getSummary());
      setErrorSaisie12(message.getSummary());
    } else {
      setErrorSaisie11("");
      setErrorSaisie12("");
    }
}
  • Zeile 4: Die submit-Methode ruft die validateForm-Methode auf, um die abschließenden Validierungen durchzuführen,
  • Zeile 11: Wir prüfen, ob input11 + input12 = 10 ist;
  • falls nicht, Zeilen 13–14, wird eine FacesMessage mit der ID `saisies11et12.incorrectes` erstellt. Die Meldung lautet wie folgt:

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
  • Die auf diese Weise erstellte Meldung wird (Zeilen 15–16) zur Liste der Fehlermeldungen der Anwendung hinzugefügt. Diese Meldung ist nicht mit einer bestimmten Komponente verknüpft. Es handelt sich um eine globale Anwendungsmeldung. Sie wird durch das oben gezeigte Tag <h:messages globalOnly="true"/> angezeigt,
  • Zeile 18: Wir erstellen eine neue FacesMessage mit der ID „error.sign“. Sie sieht wie folgt aus:

error.sign="!"

Wir haben erwähnt, dass die statische Methode [Messages.getMessage] ein FacesMessage mit einer Zusammenfassungsversion und einer Detailversion erstellt, sofern diese vorhanden sind. Hier existiert nur die Zusammenfassungsversion der error.sign-Meldung. Wir erhalten die Zusammenfassung einer Nachricht m mit m.getSummary(). In den Zeilen 19 und 20 wird die Zusammenfassung der Nachricht „error.sign“ in die Felder „errorSaisie11“ und „errorSaisie12“ des Modells gesetzt. Sie werden durch die folgenden JSF-Tags angezeigt:


          <h:outputText value="#{form.saisie11}"/>
          ...
          <h:outputText value="#{form.saisie12}"/>
  • Zeilen 22–23: Wenn die Bedingung `saisie11 + saisie12 = 10` wahr ist, werden die beiden Felder `errorSaisie11` und `errorSaisie12` im Modell gelöscht, sodass alle vorherigen Fehlermeldungen entfernt werden. Dabei ist zu beachten, dass das Modell zwischen den Anfragen in der Sitzung des Clients beibehalten wird.

Hier ein Ausführungsbeispiel:

Beachten Sie in Spalte [1], dass das Modell die übermittelten Werte erhalten hat, was darauf hinweist, dass alle Validierungs- und Konvertierungsvorgänge zwischen den übermittelten Werten und dem Modell erfolgreich waren. Der Ereignis-Handler „form.submit“, der den Klick auf die Schaltfläche [Validate] verarbeitet, konnte somit ausgeführt werden. Es ist dieser Handler, der die in [2] und [3] angezeigten Meldungen generiert hat. Wir sehen, dass das Modell aktualisiert wurde, obwohl das Formular abgelehnt und an den Client zurückgesendet wurde. Möglicherweise möchten Sie, dass das Modell in einem solchen Fall nicht aktualisiert wird. Wenn der Benutzer die Aktualisierung über die Schaltfläche [Abbrechen] [4] abbricht, können Sie nämlich nicht zum ursprünglichen Modell zurückkehren, es sei denn, Sie haben es gespeichert.

2.8.5.9. Ein Formular per POST senden, ohne die Eingaben zu validieren

Betrachten Sie das obige Formular und nehmen Sie an, der Benutzer möchte die Formularübermittlung abbrechen, ohne sich seiner Fehler bewusst zu sein. Er wird dann die Schaltfläche [Abbrechen] verwenden, die durch den folgenden JSF-Code generiert wird:


<!-- boutons de commande -->
      <h:panelGrid columns="2">
        <h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
        <h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
      </h:panelGrid>

Zeile 4, die Nachricht msg['cancel'] lautet wie folgt:


cancel=Annuler

Die mit der Schaltfläche [Abbrechen] verknüpfte Methode „form.cancel“ wird nur ausgeführt, wenn das Formular gültig ist. Dies haben wir bereits für die mit der Schaltfläche [Absenden] verknüpfte Methode „form.submit“ gezeigt. Wenn der Benutzer die Formularübermittlung abbrechen möchte, besteht natürlich keine Notwendigkeit, die Gültigkeit seiner Eingaben zu prüfen. Dies wird durch das Attribut immediate="true" erreicht, das JSF anweist, die Methode form.cancel auszuführen, ohne die Validierungs- und Konvertierungsphase zu durchlaufen. Kehren wir zum JSF-POST-Verarbeitungszyklus zurück:

Ereignisse für Aktionskomponenten <h:commandButton> und <h:commandLink> mit dem Attribut immediate="true" werden in Phase [C] verarbeitet, woraufhin der JSF-Zyklus direkt zu Phase [E] übergeht, um die Antwort darzustellen.

Die Methode form.cancel lautet wie folgt:


  public String cancel() {
    saisie1 = 0;
    saisie2 = 0;
    saisie3 = 0;
    saisie4 = 0;
    saisie5 = 0.0;
    saisie6 = 0.0;
    saisie7 = true;
    saisie8 = new Date();
    saisie9 = "";
    saisie10 = 0;
    return null;
}

Wenn Sie im vorherigen Formular auf die Schaltfläche [Abbrechen] klicken, wird die folgende Seite angezeigt:

  • Das Formular wird erneut angezeigt, da der Ereignisbehandler „form.cancel“ den Navigationsschlüssel auf null setzt. Daher wird die Seite [index.xhtml] zurückgegeben,
  • das Modell [Form.java] wurde durch die Methode form.cancel geändert. Dies spiegelt sich in Spalte [2] wider, die dieses Modell anzeigt,
  • während Spalte [3] den gesendeten Wert für die Komponenten widerspiegelt.

Kehren wir zum JSF-Code für die Komponente saisie1 [4] zurück;


          <!-- ligne 1 -->
          <h:outputText value="#{msg['saisie1.prompt']}"/>
          <h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
          <h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>

Zeile 4: Der Wert der Komponente „saisie1“ ist an das Modell „form.saisie1“ gebunden. Dies hat mehrere Auswirkungen:

  • Bei einer GET-Anfrage an [index.xhtml] zeigt die Komponente „saisie1“ den Wert des Modells „form.saisie1“ an.
  • bei einer POST-Anfrage an [index.xhtml] wird der für die Komponente „saisie1“ übermittelte Wert dem Modell „form.saisie1“ nur dann zugewiesen, wenn alle Formularvalidierungen und -konvertierungen erfolgreich waren. Unabhängig davon, ob das Modell durch die übermittelten Werte aktualisiert wurde oder nicht: Wenn das Formular nach der POST-Anfrage übermittelt wird, zeigen die Komponenten den übermittelten Wert an und nicht den Wert des ihnen zugeordneten Modells. Dies ist im obigen Screenshot zu sehen, wo die Spalten [2] und [3] nicht dieselben Werte aufweisen.

2.9. Beispiel mv-jsf2-07: Ereignisse im Zusammenhang mit Änderungen des Zustands von JSF-Komponenten

2.9.1. Die Anwendung

Die Anwendung zeigt ein Beispiel für eine POST-Anfrage, die ohne Verwendung einer Schaltfläche oder eines Links ausgeführt wird. Das Formular sieht wie folgt aus:

Der Inhalt von combo2 [2] ist mit dem in combo1 [1] ausgewählten Element verknüpft. Wenn die Auswahl in [1] geändert wird, wird eine POST-Anfrage gesendet, bei der der Inhalt von combo2 aktualisiert wird, um das in [1] ausgewählte Element widerzuspiegeln, und anschließend wird das Formular übermittelt. Während dieser POST-Anfrage wird keine Validierung durchgeführt.

2.9.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Es gibt ein einziges Formular [index.xhtml] mit seiner Vorlage [Form.java].

2.9.3. Die Anwendungsumgebung

Die Meldungsdatei [messages_fr.properties]:


app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire

Das Stylesheet [styles.css]:


.info{
   font-family: Arial,Helvetica,sans-serif;
   font-size: 14px;
   font-weight: bold
}
 
.col1{
   background-color: #ccccff
}
 
.col2{
   background-color: #ffcccc
}
 
.col3{
   background-color: #ffcc66
}
 
.col4{
   background-color: #ccffcc
}
 
.error{
   color: #ff0000
}
 
.saisie{
   background-color: #ffcccc;
   border-color: #000000;
   border-width: 5px;
   color: #cc0033;
   font-family: cursive;
   font-size: 16px
}
 
.combo{
  color: green;
}
 
.entete{
   font-family: 'Times New Roman',Times,serif;
   font-size: 14px;
   font-weight: bold
}

2.9.4. Das Formular [index.xhtml]

Das Formular [index.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
    ...
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
    <h:form id="formulaire">
      <h:messages globalOnly="true"/>
      <h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
        <!-- headers -->
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
        <!-- line 1 -->
        <h:outputText value="#{msg['combo1.prompt']}"/>
        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
        </h:selectOneMenu>
        <h:panelGroup></h:panelGroup>
        <h:outputText value="#{form.combo1}"/>
        <!-- line 2 -->
        <h:outputText value="#{msg['combo2.prompt']}"/>
        <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
        </h:selectOneMenu>
        <h:panelGroup></h:panelGroup>
        <h:outputText value="#{form.combo2}"/>
        <!-- line 3 -->
        <h:outputText value="#{msg['saisie1.prompt']}"/>
        <h:inputText id="saisie1" value="#{form.saisie1}" required="true" requiredMessage="#{msg['data.required']}" styleClass="saisie" converterMessage="#{msg['integer.required']}"/>
        <h:message for="saisie1" styleClass="error"/>
        <h:outputText value="#{form.saisie1}"/>
      </h:panelGrid>
      <!-- control buttons -->
      <h:panelGrid columns="2" border="0">
        <h:commandButton value="#{msg['submit']}"/>
        ...
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

Die neue Funktion befindet sich im Code für combo1, Zeilen 24–26. Es erscheinen neue Attribute:

  • onchange: HTML-Attribut – deklariert eine Funktion oder JavaScript-Code, der ausgeführt werden soll, wenn sich das ausgewählte Element in combo1 ändert. Hier übermittelt der JavaScript-Code submit() das Formular an den Server,
  • valueChangeListener: JSF-Attribut – gibt den Namen der Methode an, die auf dem Server ausgeführt werden soll, wenn sich der ausgewählte Eintrag in combo1 ändert. Insgesamt werden zwei Methoden ausgeführt: eine auf der Client-Seite, die andere auf der Server-Seite,
  • immediate=true: JSF-Attribut – legt den Zeitpunkt fest, zu dem der serverseitige Ereignis-Handler ausgeführt werden soll: nachdem das Formular entsprechend der Benutzereingabe neu aufgebaut wurde, aber vor der Eingabevalidierung. Das Ziel hierbei ist es, die Liste combo2 basierend auf dem in der Liste combo1 ausgewählten Element zu füllen, selbst wenn an anderer Stelle im Formular ungültige Einträge vorhanden sind. Hier ein Beispiel:
  • In [1] ist ein Anfangseintrag vorhanden,
  • in [2] ändern wir den ausgewählten Eintrag in combo1 von A auf B.

Das Ergebnis sieht wie folgt aus:

Die POST-Anfrage wurde gesendet. Der Inhalt von combo2 [2] wurde an den in combo1 [1] ausgewählten Eintrag angepasst, obwohl die Eingabe [3] falsch war. Das Attribut immediate=true führte dazu, dass die Methode form.combo1ChangeListener vor den Validierungsprüfungen ausgeführt wurde. Ohne dieses Attribut wäre sie nicht ausgeführt worden, da der Verarbeitungszyklus aufgrund des Fehlers in [3] bei den Gültigkeitsprüfungen gestoppt worden wäre.

Die mit dem Formular verbundenen Meldungen lauten in [messages.properties] wie folgt:


app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire

Der Geltungsbereich von [Form.java] ist auf „request“ festgelegt:


package forms;
 
...
 
@ManagedBean
@RequestScoped
public class Form {

Zeile 6: Wir legen den Gültigkeitsbereich des Beans auf „request“ fest.

2.9.5. Die Klasse [Form.java]

Die Klasse [Form.java] sieht wie folgt aus:


package forms;
 
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
 
@ManagedBean
@RequestScoped
public class Form {
  
  public Form() {
  }
  
// form fields
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // working fields
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // methods
  public SelectItem[] getCombo1Items(){
    // init combo1
    SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
    for(int i=0;i<combo1Labels.length;i++){
      combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
    }
    return combo1Items;
  }
  
  public SelectItem[] getCombo2Items(){
    // init combo2 as a function of combo1
    SelectItem[] combo2Items=new SelectItem[5];
    for(int i=1;i<=combo2Items.length;i++){
      combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
    }
    return combo2Items;
  }
  
  // listeners
  public void combo1ChangeListener(ValueChangeEvent event){
    // follow-up
    logger.info("combo1ChangeListener");
    // retrieve the posted value of combo1
    combo1Label=(String)event.getNewValue();
    // we return the answer because we want to short-circuit the validations
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // follow-up
    logger.info("raz");
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
  
// getters - setters
  ...
}

Verbinden wir das Formular [index.xhtml] mit seinem Modell [Form.java]:

Die Liste combo1 wird durch den folgenden JSF-Code generiert:


        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>

Es ruft seine Elemente über die Methode getCombo1Items seines Modells ab (Zeile 2). Diese Methode ist in den Zeilen 28–35 des Java-Codes definiert. Sie generiert eine Liste mit drei Elementen: {"A", "B", "C"}.

Die Liste „combo2“ wird durch den folgenden JSF-Code generiert:


        <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>

Es bezieht seine Elemente über die Methode getCombo2Items seines Modells (Zeile 2). Diese Methode ist in den Zeilen 37–44 des Java-Codes definiert. Sie generiert eine Liste mit fünf Elementen {"X1", "X2", "X3", "X4", "X5"}, wobei X das Element combo1Label aus Zeile 16 ist. Daher enthält die combo2-Liste bei der anfänglichen Generierung des Formulars die Elemente {"A1", "A2", "A3", "A4", "A5"}.

Wenn der Benutzer den ausgewählten Eintrag in der Liste „combo1“ ändert,

  • wird das Ereignis onchange="submit();" vom Client-Browser verarbeitet. Das Formular wird dann an den Server übermittelt,
  • auf der Serverseite erkennt JSF, dass die „combo1“-Komponente ihren Wert geändert hat. Die Methode „combo1ChangeListener“ in den Zeilen 47–54 wird ausgeführt. Eine „ValueChangeListener“-Methode erhält ein Objekt vom Typ „javax.faces.event.ValueChangeEvent“ als Parameter. Mit diesem Objekt können Sie die alten und neuen Werte der geänderten Komponente mithilfe der folgenden Methoden abrufen:

Image

Hier ist die Komponente die Liste „combo1“ vom Typ „UISelectOne“. Ihr Wert ist vom Typ „String“.

  • Zeile 51 des Java-Modells: Der neue Wert von combo1 wird in combo1Label gespeichert, das zur Generierung der Elemente in der Liste combo2 verwendet wird.
  • Zeile 53: Die Antwort wird gesendet. Es ist wichtig zu beachten, dass der Handler combo1ChangeListener mit dem Attribut immediate="true" ausgeführt wird. Er wird daher ausgeführt, nachdem der Seitenkomponentenbaum mit den übermittelten Werten aktualisiert wurde und bevor der Validierungsprozess für die übermittelten Werte stattfindet. Wir möchten diesen Validierungsprozess jedoch umgehen, da die Liste combo2 aktualisiert werden muss, auch wenn an anderer Stelle im Formular noch ungültige Einträge vorhanden sind. Wir fordern daher an, dass die Antwort sofort gesendet wird, ohne die Eingabevalidierungsphase zu durchlaufen.
  • Das Formular wird genau so zurückgesendet, wie es eingegeben wurde. Die Elemente in den Listen combo1 und combo2 sind jedoch keine übermittelten Werte. Sie werden durch den Aufruf der Methoden getCombo1Items und getCombo2Items neu generiert. Die letztere Methode verwendet dann den neuen Wert von combo1Label, der von combo1ChangeListener gesetzt wurde, und die Elemente in der Liste combo2 ändern sich.

2.9.6. Die Schaltfläche [ Reset]

Wir möchten, dass die Schaltfläche [Zurücksetzen] das Formular in seinen Ausgangszustand zurückversetzt, wie unten gezeigt:

In [1] wird das Formular vor dem [Raz]-Button gesendet; in [2] das Ergebnis des POST-Vorgangs.

Obwohl funktional einfach, erweist sich die Umsetzung dieses Anwendungsfalls als recht komplex. Wir können verschiedene Lösungen ausprobieren, darunter auch diejenige, die im vorherigen Beispiel für die Schaltfläche [Abbrechen] verwendet wurde:


       <h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>

wobei die Methode form.raz wie folgt lautet:


  public String raz(){
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
}

Das Ergebnis, das durch Klicken auf die Schaltfläche [Löschen] im vorherigen Beispiel erzielt wird, lautet wie folgt:

Spalte [1] zeigt an, dass die Methode „form.raz“ ausgeführt wurde. In Spalte [1] werden jedoch weiterhin die übermittelten Werte angezeigt:

  • Für combo1 war der übergebene Wert „B“. Dieser Eintrag ist daher in der Liste ausgewählt,
  • für combo2 war der übergebene Wert „B5“. Durch die Ausführung von form.raz wurden die Elemente {„B1“, …, „B5“} von combo2 in {„A1“, …, „A5“} geändert. Das Element „B5“ existiert nicht mehr und kann daher nicht ausgewählt werden. Es wird dann das erste Element in der Liste angezeigt,
  • für input1 war der übergebene Wert 10.

Dies ist das normale Verhalten mit dem Attribut immediate="true". Um ein anderes Ergebnis zu erzielen, müssen Sie die Werte, die Sie im neuen Formular sehen möchten, übermitteln, auch wenn der Benutzer andere Werte eingegeben hat. Dies wird mit etwas clientseitigem JavaScript erreicht. Das Formular sieht dann wie folgt aus:


<script language="javascript">
  function raz(){
    document.forms['formulaire'].elements['formulaire:combo1'].value="A";
    document.forms['formulaire'].elements['formulaire:combo2'].value="A1";
    document.forms['formulaire'].elements['formulaire:saisie1'].value=0;
    //document.forms['form'].submit();
  }
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
  • Zeile 10: Das Attribut onclick='raz()' weist den Browser an, die JavaScript-Funktion raz auszuführen, wenn der Benutzer auf die Schaltfläche [Zurücksetzen] klickt.
  • Zeile 3: Dem HTML-Element mit dem Namen 'form:combo1' wird der Wert „A“ zugewiesen. Die verschiedenen Elemente in Zeile 3 sind wie folgt:
    • document: die vom Browser angezeigte Seite,
    • document.forms: alle Formulare im Dokument,
    • document.forms['form']: das Formular mit dem Attribut name="form",
    • document.forms['form'].elements: alle Elemente des Formulars mit dem Attribut name="form",
    • document.forms['form'].elements['form:combo1']: das Formularelement mit dem Attribut „name“ mit dem Wert „form:combo1“
    • document.forms['form'].elements['form:combo1'].value: Wert, der vom Formularelement mit dem Attribut name="form:combo1" übermittelt wird.

Um die name-Attribute der verschiedenen Elemente auf der vom Browser angezeigten Seite zu finden, können Sie deren Quellcode anzeigen (unten mit IE7):

<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>

Nachdem dies nun erklärt ist, sehen wir, dass im JavaScript-Code der raz-Funktion:

  • Zeile 3 sicherstellt, dass der für die Komponente „combo1“ übermittelte Wert die Zeichenfolge „A“ ist,
  • Zeile 4 stellt sicher, dass der für die Komponente „combo2“ übermittelte Wert die Zeichenfolge „A1“ ist,
  • Zeile 5 stellt sicher, dass der für die Komponente „saisie1“ übermittelte Wert die Zeichenfolge „0“ ist.

Sobald dies geschehen ist, wird der Formular-POST-Vorgang ausgelöst, der mit einer beliebigen Schaltfläche vom Typ <h:commandButton> verknüpft ist (Zeile 10). Die Methode form.raz wird ausgeführt, und das Formular wird so zurückgegeben, wie es gesendet wurde. Wir erhalten dann das folgende Ergebnis:

Dieses Ergebnis verbirgt viele Details. Die Werte „A“, „A1“ und „0“ aus den Komponenten combo1, combo2 und saisie1 werden an den Server gesendet. Angenommen, der vorherige Wert von combo1 war „B“. Dann hat sich der Wert der Komponente combo1 geändert, und die Methode form.combo1ChangeListener sollte ebenfalls ausgeführt werden. Wir haben zwei Ereignisbehandler mit dem Attribut immediate="true". Werden beide ausgeführt? Wenn ja, in welcher Reihenfolge? Nur einer? Wenn ja, welcher?

Um mehr darüber zu erfahren, erstellen wir Protokolle in der Anwendung:


package forms;
 
import java.util.logging.Logger;
...
public class Form {
  
...  
// form fields
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // working fields
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // listener
  public void combo1ChangeListener(ValueChangeEvent event){
    // follow-up
    logger.info("combo1ChangeListener");
    // retrieve the posted value of combo1
    combo1Label=(String)event.getNewValue();
    // we return the answer because we want to short-circuit the validations
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // follow-up
    logger.info("raz");
    // raz du formulaire
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
...
}
  • Zeile 16: Es wird ein Logger erstellt. Der Parameter getLogger ermöglicht es uns, die Quellen der Protokolle zu unterscheiden. Hier heißt der Logger forms.Form,
  • Zeile 21: Der Aufruf der Methode „combo1ChangeListener“ wird protokolliert,
  • Zeile 30: Der Aufruf der Methode „raz“ wird protokolliert.

Welche Protokolle werden durch die Schaltfläche [Löschen] oder bei einer Änderung des Werts von combo1 erzeugt? Betrachten wir verschiedene Szenarien:

  • Wir verwenden die Schaltfläche [Clear], während der ausgewählte Eintrag in combo1 „A“ ist. „A“ ist daher der letzte Wert der Komponente combo1. Wir haben gesehen, dass die Schaltfläche [Clear] eine JavaScript-Funktion ausführt, die den Wert „A“ für die Komponente combo1 übermittelt. Die Komponente combo1 ändert daher ihren Wert nicht. Die Protokolle zeigen dann, dass nur die Methode form.raz ausgeführt wird:
  
  • Wir verwenden die Schaltfläche [Clear], solange der ausgewählte Eintrag in combo1 nicht „A“ ist. Die Komponente combo1 ändert daher ihren Wert: Ihr letzter Wert war nicht „A“, und die Schaltfläche [Clear] setzt ihn auf „A“. Die Protokolle zeigen dann, dass zwei Methoden in der folgenden Reihenfolge ausgeführt werden: combo1ChangeListener, raz:
  
  • Wir ändern den Wert von combo1, ohne die [Raz]-Taste zu verwenden. Die Protokolle zeigen, dass nur die Methode combo1ChangeListener ausgeführt wird:
  

2.10. Beispiel mv-jsf2-08: das <h:dataTable>-Tag

2.10.1. Die Anwendung

Die Anwendung zeigt eine Liste von Personen an, mit der Option, diese zu löschen:

  • in [1] eine Liste von Personen,
  • in [2] die Links, über die Sie diese löschen können.

2.10.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Es gibt ein einziges Formular [index.xhtml] mit seiner Vorlage [Form.java].

2.10.3. Die Anwendungsumgebung

Die Konfigurationsdatei [faces-config.xml]:


<?xml version='1.0' encoding='UTF-8'?>
 
<!-- =========== FULL CONFIGURATION FILE ================================== -->
 
<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
 
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
</faces-config>

Die Nachrichtendatei [messages_fr.properties]:


app.titre=intro-08
app.titre2=JSF - DataTable
submit=Valider
personnes.headers.id=Id
personnes.headers.nom=Nom
personnes.headers.prenom=Pr\u00e9nom

Das Stylesheet [styles.css]:


.headers {
   text-align: center;
   font-style: italic;
   color: Snow;
   background: Teal;
}
 
.id {
   height: 25px;
   text-align: center;
   background: MediumTurquoise;
}
 
.nom {
   text-align: left;
   background: PowderBlue;
}
.prenom {
   width: 6em;
   text-align: left;
   color: Black;
   background: MediumTurquoise;
}

2.10.4. Das Formular [index.xhtml] und seine Vorlage [Form.java]

Sehen wir uns die Ansicht an, die mit der Seite [index.xhtml] verknüpft ist:

  

Das Formular [index.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
    <h:form id="formulaire">
      <h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
  ........................
      </h:dataTable>
    </h:form>
  </h:body>
</html>

Zeile 14: Das <h:dataTable>-Tag verwendet das Feld #{form.personnes} als Datenquelle. Es sieht wie folgt aus:

private List<Person> people;

Die Klasse [Person] sieht wie folgt aus:


package forms;
 
public class Personne {
  // data
  private int id;
  private String nom;
  private String prénom;
  
  // manufacturers
  public Personne(){
    
  }
  
  public Personne(int id, String nom, String prénom){
    this.id=id;
    this.nom=nom;
    this.prénom=prénom;
  }
  
  // toString
  public String toString(){
    return String.format("Personne[%d,%s,%s]", id,nom,prénom);
  }
  
  // getter and setters
...
}

Kehren wir zum Inhalt des <h:dataTable>-Tags zurück:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
  • Das Attribut var="person" legt den Namen der Variablen fest, die die aktuelle Person innerhalb des <h:dataTable>-Tags repräsentiert,
  • das Attribut headerClass="headers" legt den Stil der Tabellenkopfzeilen fest,
  • das Attribut `columnClasses="...."` legt den Stil jeder Spalte in der Tabelle fest.

Sehen wir uns eine der Spalten der Tabelle an und schauen wir uns an, wie sie aufgebaut ist:

  

Der XHTML-Code für die Spalte „Id“ lautet wie folgt:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.id']}"/>
          </f:facet>
          <h:outputText value="#{personne.id}"/>
        </h:column>
        ...
      </h:dataTable>
 
lignes 3-5 : la balise <f:facet name="header"> définit le titre de la colonne,
ligne 4 : le titre de la colonne est pris dans le fichier des messages,
ligne 6 : personne fait référence à l'attribut var de la balise <h:dataTable ...> (ligne 1). On écrit donc l'id de la personne courante.
 
 
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.id']}"/>
          </f:facet>
          <h:outputText value="#{personne.id}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.nom']}"/>
          </f:facet>
          <h:outputText value="#{personne.nom}"/>
        </h:column>
        <h:column>
          <f:facet name="header">
            <h:outputText value="#{msg['personnes.headers.prenom']}"/>
          </f:facet>
          <h:outputText value="#{personne.prénom}"/>
        </h:column>
...
      </h:dataTable>
  • Zeilen 3–7: die ID-Spalte der Tabelle,
  • Zeilen 8–13: die Nachnamensspalte der Tabelle,
  • Zeilen 14–19: die Spalte mit den Vornamen der Tabelle.

Betrachten wir nun die Spalte mit dem Link [Entfernen]:

Diese Spalte wird durch den folgenden Code generiert:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
        <h:column>
          <h:commandLink value="Retirer" action="#{form.retirerPersonne}">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
          </h:commandLink>
        </h:column>
      </h:dataTable>

Der Link [Entfernen] wird durch die Zeilen 4–6 generiert. Wenn der Link angeklickt wird, wird die Methode [Form].removePerson ausgeführt. Es ist an der Zeit, die Klasse [Form.java] zu untersuchen:


package forms;
 
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
 
@ManagedBean
@SessionScoped
public class Form {
 
  // model
  private List<Personne> personnes;
  private int personneId;
 
  // manufacturer
  public Form() {
    // initialization of the list of persons
    personnes = new ArrayList<Personne>();
    personnes.add(new Personne(1, "dupont", "jacques"));
    personnes.add(new Personne(2, "durand", "élise"));
    personnes.add(new Personne(3, "martin", "jacqueline"));
  }
 
  public String retirerPersonne() {
    // search for the selected person
    int i = 0;
    for (Personne personne : personnes) {
      // current person = selected person?
      if (personne.getId() == personneId) {
        // delete the current person from the list
        personnes.remove(i);
        // we're done
        break;
      } else {
        // next person
        i++;
      }
    }
    // we test on the same page
    return null;
  }
  
  // getters and setters
...
}
  • Zeilen 18–24: Der Konstruktor initialisiert die Liste der Personen aus Zeile 14,
  • Zeile 10: Da diese Liste über mehrere Anfragen hinweg bestehen bleiben muss, ist der Gültigkeitsbereich der Bean die Sitzung.

Wenn die Methode [removePerson] in Zeile 26 ausgeführt wird, wurde das Feld in Zeile 15 mit der ID der Person initialisiert, deren [Remove]-Link angeklickt wurde:


          <h:commandLink value="Retirer" action="#{form.retirerPersonne}">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>

Das <f:setPropertyActionListener>-Tag ermöglicht die Übertragung von Informationen an das Modell. Hier wird der Wert des value-Attributs in das durch das target-Attribut identifizierte Modellfeld kopiert. Somit wird die ID der aktuellen Person – die aus der Personenliste entfernt werden soll – über den Getter dieses Feldes in das Feld [Form].personneId kopiert. Dies geschieht, bevor die durch das action-Attribut in Zeile 1 referenzierte Methode ausgeführt wird.

Zeilen 26–43: Die Methode [supprimerPersonne] entfernt die Person, deren ID mit *personId* übereinstimmt.

2.11. Beispiel mv-jsf2-09: Aufbau einer JSF-Anwendung

2.11.1. Die Anwendung

Die Anwendung veranschaulicht, wie eine JSF-Anwendung mit zwei Ansichten gestaltet wird:

Die Anwendung verfügt über zwei Ansichten:

  • in [1], Seite 1,
  • in [2], Seite 2.

Sie können zwischen den beiden Seiten navigieren. Wir möchten hier zeigen, dass die Seiten 1 und 2 ein gemeinsames Layout haben, wie in den Screenshots oben zu sehen ist.

2.11.2. Das NetBeans-Projekt

Das NetBeans-Projekt für die Anwendung sieht wie folgt aus:

Die Anwendung besteht ausschließlich aus XHTML-Seiten. Es gibt keine zugehörige Java-Vorlage.

2.11.3. Die Seite [layout.xhtml]

Die Seite [layout.xhtml] definiert das Layout der Seiten der Anwendung:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
          <td>
            <ui:insert name="contenu" >
              <h2>Contenu</h2>
            </ui:insert>
          </td>
        </tr>
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>
  </h:body>
</html>

In Zeile 7 erscheint ein neuer Namespace, **ui**. Dieser Namespace enthält die Tags, die zur Formatierung der Seiten einer Anwendung verwendet werden. Die Tags in diesem Namespace werden in den Zeilen 17, 22, 25 und 32 verwendet.

Die Seite [layout.xhtml] zeigt Informationen in einer HTML-Tabelle an (Zeile 14). Sie können diese Seite über einen Browser aufrufen:

  • in [1], die angeforderte URL.

Der Bereich [2] wurde durch den folgenden XHTML-Code generiert:


  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
...       
      </table>
    </h:form>
</h:body>

Das <ui:include>-Tag in Zeile 6 ermöglicht es, externen XHTML-Code in die Seite einzubinden. Die Datei [entete.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>entête</h2>
  </body>
</html>

Der gesamte Code aus den Zeilen 3–8 wird in [layout.xhtml] eingefügt. Somit werden die Tags <html> und <body> innerhalb eines <td>-Tags eingefügt. Dies verursacht keine Fehler. Daher sind über <ui:include> eingebundene Seiten vollständige XHTML-Seiten. Optisch hat nur Zeile 6 eine Auswirkung. Die Tags <html> und <body> sind aus syntaktischen Gründen vorhanden.

Bereich [3] wurde durch den folgenden XHTML-Code generiert:


<h:form id="formulaire">
      <table style="width: 400px">
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
...
        </tr>
...
      </table>
    </h:form>

Das <ui:include>-Tag in Zeile 5 bindet die folgende Datei [menu.xhtml] ein:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>menu</h2>
  </body>
</html>

Bereich [4] wurde durch den folgenden XHTML-Code generiert:


<h:form id="formulaire">
      <table style="width: 400px">
...
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>

Das <ui:include>-Tag in Zeile 6 bindet die folgende Datei [basdepage.xhtml] ein:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
  <body>
    <h2>bas de page</h2>
  </body>
</html>

Bereich [5] wurde durch den folgenden XHTML-Code generiert:


    <h:form id="formulaire">
...
          <td>
            <ui:insert name="contenu" >
              <h2>Contenu</h2>
            </ui:insert>
          </td>
 ...
      </table>
</h:form>

Das <ui:insert>-Tag in Zeile 5 definiert einen Bereich namens „content“. Dies ist ein Bereich, der variablen Inhalt aufnehmen kann. Wir werden sehen, wie das funktioniert. Als wir die Seite [layout.xhtml] aufgerufen haben, war für den Bereich „content“ kein Inhalt definiert. In diesem Fall wird der Inhalt des <ui:insert>-Tags in den Zeilen 4–6 verwendet. Daher wird Zeile 5 angezeigt.

2.11.4. Die Seite [page1.xhtml]

Die Seite [layout.xhtml] ist nicht zur Anzeige bestimmt. Sie dient als Vorlage für die Seiten [page1.xhtml] und [page2.xhtml]. Dies wird als Seitenvorlage bezeichnet. Die Seite [page1.xhtml] sieht wie folgt aus:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2>page 1</h2>
      <h:commandLink value="page 2" action="page2"/>
    </ui:define>
  </ui:composition>
</html>
  • In Zeile 6 wird der ui-Namespace verwendet;
  • in Zeile 7 legen wir mithilfe eines <ui:composition>-Tags fest, dass die Seite mit der Vorlage [layout.xhtml] verknüpft ist;
  • Zeile 8: Diese Zuordnung stellt sicher, dass jedes <ui:define>-Tag mit einem <ui:insert>-Tag in der verwendeten Vorlage verknüpft wird, in diesem Fall [layout.xhtml]. Die Verknüpfung wird über das name-Attribut beider Tags hergestellt. Diese müssen identisch sein.

Die angezeigte Seite ist [layout.xhtml], wobei der Inhalt jedes <ui:insert>-Tags durch den Inhalt des <ui:define>-Tags aus der angeforderten Seite ersetzt wird. Hier ist es so, als ob die angezeigte Seite wie folgt wäre:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h:form id="formulaire">
      <table style="width: 400px">
        <tr>
          <td colspan="2" bgcolor="#ccccff">
            <ui:include src="entete.xhtml"/>
          </td>
        </tr>
        <tr style="height: 200px">
          <td bgcolor="#ffcccc">
            <ui:include src="menu.xhtml"/>
          </td>
          <td>
              <h2>page 1</h2>
              <h:commandLink value="page 2" action="page2"/>
          </td>
        </tr>
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>
  </h:body>
</html>

Die Zeilen 25–26 von [page1.xhtml] wurden anstelle des <ui:insert>-Tags in [layout.xml] eingefügt.

Die Seite [page2.xhtml] ähnelt [page1.xhtml]:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="layout.xhtml">
    <ui:define name="contenu">
      <h2>page 2</h2>
      <h:commandLink value="page 1" action="page1"/>
    </ui:define>
  </ui:composition>
</html>

2.12. Fazit

Die hier vorgestellte Untersuchung zu JSF 2 ist bei weitem nicht vollständig. Sie reicht jedoch aus, um die folgenden Beispiele zu verstehen. Weitere Informationen finden Sie unter [ref2].

2.13. Testen mit Eclipse

Wir zeigen Ihnen, wie Sie Maven-Projekte mit der SpringSource Tool Suite testen können:

  • Importieren Sie in [1] ein Maven-Projekt [2], indem Sie auf die Schaltfläche [3] klicken. Hier verwenden wir das Maven-Projekt [mv-jsf2-09] für Eclipse
  • In [4] wurde das importierte Projekt korrekt als Maven-Projekt [5] erkannt,
  • in [6] wird das Projekt in den Projekt-Explorer importiert,
  • In [7] führen wir es auf einem Tomcat-Server [8] aus [9],
  • in [10] wurde Tomcat 7 gestartet,
  • in [11] wird die Startseite des Projekts [mv-jsf2-09] [11] in einem Browser innerhalb von Eclipse angezeigt.