Skip to content

2. Java Server Faces

Wir stellen nun das Java Server Faces-Framework vor. Es wird die Version 2 verwendet, doch die Beispiele zeigen hauptsächlich Funktionen der Version 1. Von Version 2 werden wir nur die Funktionen vorstellen, die für die nachfolgende Beispielanwendung erforderlich sind.

2.1. Die Rolle von JSF in einer Webanwendung

Zunächst wollen wir JSF in den Entwicklungsprozess einer Webanwendung einordnen. Meistens basiert diese auf einer mehrschichtigen Architektur wie der folgenden:

  • Die Schicht [web] ist die Schicht, die mit dem Benutzer der Webanwendung in Kontakt steht. Dieser interagiert mit der Webanwendung über Webseiten, die in einem Browser angezeigt werden. In dieser Schicht befindet sich JSF und ausschließlich in dieser Schicht,
  • Die Schicht [métier] implementiert die Geschäftsregeln der Anwendung, wie beispielsweise die Berechnung eines Gehalts oder einer Rechnung. Diese Schicht nutzt Daten, die vom Benutzer über die Schicht [web] und vom Datenbanksystem über die Schicht [DAO] bereitgestellt werden,
  • die Schicht [DAO] (Data Access Objects), die Schicht [jpa] (Java Persistence API) und der Treiber JDBC verwalten den Zugriff auf die Daten des DBMS. Die Schicht [jpa] dient als ORM (Objekt-Relational-Mapper). Sie bildet eine Brücke zwischen den von der Schicht [DAO] verarbeiteten Objekten und den Zeilen und Spalten der Daten einer relationalen Datenbank,
  • die Integration der Schichten kann über einen Spring-Container oder EJB3 (Enterprise Java Bean) erfolgen.

Die im Folgenden zur Veranschaulichung von JSF angeführten Beispiele verwenden nur eine einzige Schicht, nämlich die Schicht [web]:

Sobald die Grundlagen von JSF erworben sind, werden wir mehrschichtige Java-Anwendungen EE erstellen.

2.2. Das Entwicklungsmodell MVC von JSF

JSF setzt das sogenannte MVC-Architekturmodell (Model – View – Controller) wie folgt um:

Diese Architektur implementiert das Entwurfsmuster MVC (Modell, Ansicht, Controller). Die Bearbeitung einer Anfrage eines Clients erfolgt in den folgenden vier Schritten:

  1. Anfrage – Der Browser des Kunden sendet eine Anfrage an den Controller [Faces Servlet]. Dieser empfängt alle Anfragen der Kunden. Er ist das Eingangstor der Anwendung. Er entspricht dem „C“ in MVC,
  2. Verarbeitung – Der Controller C verarbeitet diese Anfrage. Dabei wird er von anwendungsspezifischen Ereignisbehandlungsroutinen der geschriebenen Anwendung [2a] unterstützt. Diese Ereignisbehandlungsroutinen benötigen möglicherweise Unterstützung durch die Geschäftslogik-Schicht [2b]. Sobald die Anfrage des Kunden bearbeitet wurde, kann dies verschiedene Antworten nach sich ziehen. 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 Kunden gesendet werden soll. Die Auswahl der an den Kunden zu sendenden Antwort erfordert mehrere Schritte:
    • Auswahl des Facelets, das die Antwort generieren soll. Dies wird als Ansicht V bezeichnet, das V von MVC. Diese Auswahl hängt in der Regel vom Ergebnis der Ausführung der vom Benutzer angeforderten Aktion ab;
    • diesem Facelet die Daten zur Verfügung stellen, 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 Modell M der Ansicht, das M von MVC,

Schritt 3 besteht also in der Auswahl einer Ansicht V und der Erstellung des dafür erforderlichen Modells M.

  1. Antwort – Der Controller C fordert das ausgewählte Facelet auf, sich anzuzeigen. Dieses 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 unterschiedlich sein: Es kann sich um einen HTML-Stream, PDF, Excel 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 Ansichten V werden durch Seiten implementiert, die die Facelets-Technologie nutzen,
  • Die M-Muster und die Ereignisbehandler werden durch Java-Klassen implementiert, die oft als „Backing Beans“ oder einfach als „Beans“ bezeichnet werden.

Lassen Sie uns nun den Zusammenhang zwischen der Webarchitektur MVC und der Schichtenarchitektur näher erläutern. Es handelt sich um zwei unterschiedliche Konzepte, die manchmal verwechselt werden. Betrachten wir eine einschichtige Webanwendung JSF:

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

Betrachten wir nun eine mehrschichtige Webarchitektur:

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

In MVC haben wir gesagt, dass das Modell M das der Ansicht V, c.a.d, ist. Die Gesamtheit der von der Ansicht V angezeigten Daten. Eine weitere Definition des Modells M von MVC wird häufig angegeben:

Viele Autoren sind der Ansicht, dass der Bereich rechts von der Ebene [web] das Modell M von MVC bildet. Um Mehrdeutigkeiten zu vermeiden, sprechen wir von:

  • vom Domänenmodell, wenn alles gemeint ist, was rechts von der Ebene [web] liegt,
  • vom Modell der Ansicht, wenn die von einer Ansicht V angezeigten Daten gemeint sind.

Im Folgenden bezeichnet der Begriff „Modell M“ ausschließlich das Modell einer Ansicht V.

2.3. Beispiel mv-jsf2-01: Die Elemente eines JSF-Projekts

Die ersten Beispiele beschränken sich ausschließlich auf die mit JSF 2 implementierte Web-Schicht:

Sobald die Grundlagen erworben sind, werden wir komplexere Beispiele mit mehrschichtigen Architekturen untersuchen.

2.3.1. Projektgenerierung

Wir generieren unser erstes Projekt JSF2 mit NetBeans 7.

  
  • in [1], erstellen Sie ein neues Projekt,
  • in [2], wählen Sie die Kategorie [Maven] und den Projekttyp [Web Application],
  • in [3] den übergeordneten Ordner des neuen Projektordners festlegen,
  • in [4], geben Sie dem Projekt einen Namen,
  • in [5] einen Server auswählen. Mit NetBeans 7 hat man die Wahl zwischen den Servern Apache Tomcat und GlassFish. Der Unterschied zwischen den beiden besteht darin, dass GlassFish EJB (Enterprise Java Bean) unterstützt und Tomcat nicht. Unsere Beispiele JSF werden keine EJB verwenden. Daher kann man hier einen beliebigen Server auswählen,
  • bei [6] wählen wir die Java-Version EE 6 Web,
  • bei [7] das generierte Projekt.

Sehen wir uns die Elemente des Projekts an und erläutern wir die jeweilige Funktion.

  • in [1]: die verschiedenen Zweige des Projekts:
    • [Web Pages]: enthält die Webseiten (.xhtml, .jsp, .html), die Ressourcen (Bilder, verschiedene Dokumente), die Konfiguration der Webschicht sowie die des Frameworks JSF;
    • [Source packages]: die Java-Klassen des Projekts;
    • [Dependencies]: die für das Projekt erforderlichen .jar-Dateien, die vom Maven-Framework verwaltet werden;
    • [Java Dependencies]: die für das Projekt erforderlichen .jar-Dateien, die nicht vom Maven-Framework verwaltet werden;
    • [Project Files]: Konfigurationsdatei für Maven und NetBeans,
  • in [2]: der Zweig [Web Pages],

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>

Es handelt sich um eine Webseite, die die Zeichenfolge „Hello World“ in großen Buchstaben anzeigt.

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


<?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 in der Form http://machine:port/mv-jsf2-01/page aufgerufen werden. Der Kontext ist standardmäßig der Name des Projekts. Wir müssen diese Datei nicht ändern.

  • in [3], der Zweig [Source Packages],

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

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

Dieser Zweig zeigt alle für das Projekt erforderlichen Bibliotheken an, die 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 bereits lokal vorhanden ist, wird diese nicht erneut heruntergeladen. Wir werden sehen, dass diese Liste der Bibliotheken sowie die Repositorien, in denen sie zu finden sind, in der Konfigurationsdatei des Maven-Projekts definiert sind.

  • in [6] die für das Projekt erforderlichen Bibliotheken, die nicht von Maven verwaltet werden,
  • in [7] die Konfigurationsdateien des Maven-Projekts:
    • [nb-configuration.xml] ist die Konfigurationsdatei von NetBeans. Darauf werden wir nicht näher eingehen.
    • [pom.xml]: die Maven-Konfigurationsdatei. POM steht für „Project Object Model“. Manchmal wird es notwendig sein, direkt in diese Datei einzugreifen.

Die generierte Datei „[pom.xml]“ 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-Objekt (Artefakt), das vom Maven-Projekt erstellt wird. Diese Informationen stammen aus dem Assistenten, der bei der Erstellung des Projekts verwendet wurde:

Ein Maven-Objekt wird durch vier Eigenschaften definiert:

  • [groupId]: Eine Angabe, die einem Paketnamen ähnelt. So haben die Bibliotheken des Spring-Frameworks groupId=org.springframework, die des Frameworks JSF haben groupId=javax.faces,
  • [artifactId]: der Name des Maven-Objekts. In der Gruppe [org.springframework] finden sich somit die folgenden artifactId: spring-context, spring-core, spring-beans, ... In der Gruppe [javax.faces] finden sich die artifactId und jsf-api,
  • [version]: Versionsnummer des Maven-Artefakts. Das Artefakt org.springframework.spring-core hat somit folgende Versionen: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
  • [packaging]: Die Form 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 Angaben müssen ein Maven-Artefakt eindeutig definieren.

Die Zeilen 17–24 listen die Abhängigkeiten des Maven-Projekts auf, d. h. die Liste der für das Projekt erforderlichen Bibliotheken. Jede Bibliothek wird durch die vier Angaben (groupId, artifactId, Version, Packaging) definiert. Fehlt die Angabe packaging wie hier, wird die JAR-Datei packaging verwendet. Hinzu kommt eine weitere Angabe, „scope“, die festlegt, zu welchen Zeitpunkten im 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 erforderlich ist. Der Wert „provided“ bedeutet, dass die Bibliothek bei der Kompilierung benötigt wird, nicht jedoch bei der Ausführung. Hier wird sie bei der Ausführung vom Tomcat-7-Server bereitgestellt.

2.3.2. Ausführung des Projekts

Wir führen das Projekt aus:

In [1] wird das Maven-Projekt ausgeführt. Der Tomcat-Server wird dabei gestartet, falls er nicht bereits lief. Außerdem wird ein Browser gestartet und die Seite URL aus dem Projektkontext aufgerufen: [2]. Da kein Dokument angefordert wird, wird die Seite index.html, index.jsp, index.xhtml verwendet, sofern sie vorhanden ist. In diesem Fall ist dies die Seite [index.jsp].

2.3.3. Das Dateisystem eines Maven-Projekts

  • [1]: Das Dateisystem des Projekts befindet sich auf der Registerkarte [Files],
  • [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 (Build) des Projekts angelegt,
  • [5]: Hier hat der Projekt-Build ein Archiv mit dem Namen [mv-jsf2-01-1.0-SNAPSHOT.war] erstellt. Dieses Archiv wurde vom Tomcat-Server ausgeführt.

2.3.4. Ein Projekt für JSF konfigurieren

Unser aktuelles Projekt ist kein JSF-Projekt. Es fehlen die Bibliotheken des Frameworks JSF. Um aus dem aktuellen Projekt ein JSF-Projekt zu machen, gehen wir wie folgt vor:

  • in [1], rufen Sie die Projekteigenschaften auf,
  • in [2] wählt man die Kategorie [Frameworks] aus,
  • in [3] fügt man ein Framework hinzu,
  • in [4] wählt man „Java Server Faces“ aus,
  • in [5] schlägt NetBeans die Version 2.1 des Frameworks vor. Wir akzeptieren sie,
  • in [6] wird das Projekt dann um neue Abhängigkeiten erweitert.

Die Datei [pom.xml] wurde angepasst, 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>

In den Zeilen 14–33 wurden neue Abhängigkeiten hinzugefügt. Maven lädt diese automatisch herunter. Es bezieht sie aus sogenannten Repositories. Das zentrale Repository (Central Repository) wird automatisch verwendet. Mit dem Tag <repository> können weitere Repositories hinzugefügt werden. Hier wurden zwei Repositories hinzugefügt:

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

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>

Wir haben hier eine Datei namens XML (Zeile 1). Sie enthält die Tags aus der Datei HTML, allerdings im Format XML. Man bezeichnet dies als XHTML. Die Technologie, die zur Erstellung von Webseiten mit JSF 2 verwendet wird, heißt Facelets. Daher wird die Seite XHTML manchmal auch als Facelet-Seite bezeichnet.

In den Zeilen 3–4 wird das Tag <html> mit dem Namensraum XML (xmlns=XML Name Space) definiert.

  • Zeile 3 definiert den Hauptnamensraum http://www.w3.org/1999/xhtml,
  • Zeile 4 definiert den Namensraum http://java.sun.com/jsf/html für die Tags HTML. Diese erhalten das Präfix „h:“, wie durch xmlns:h angegeben. Diese Tags finden sich in den Zeilen 5, 7, 8 und 10.

Bei der Deklaration eines Namensraums durchsucht der Webserver die Ordner „[META-INF]“ und „Classpath“ der Anwendung nach Dateien mit der Endung .tld (TagLib Definition). Hier findet er sie im Archiv [jsf-impl.jar] und [1,2]:

Sehen wir uns die Datei [3] und die Datei [HTML_basic.tld] an:

<?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">

<!-- ============== Beschreibung der Tag-Bibliothek ============= -->

    <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-Bibliotheks-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 ebenfalls im Artefakt [jsf-impl.jar] zu finden sind.

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

Der Zweig [Other Sources] [1] enthält die Dateien, die sich im Classpath des Projekts befinden müssen und die kein Java-Code sind. Dies gilt beispielsweise für die Meldungsdateien in JSF. Wir haben gesehen, dass dieser Zweig fehlt, wenn das Framework JSF nicht zum Projekt hinzugefügt wird. Um ihn zu erstellen, muss lediglich der Ordner [src / main / resources] [3] auf der Registerkarte [Files] [2] angelegt werden.

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

Der Ordner „[WEB-INF]“ wurde angelegt und enthält die Datei „[web.xml]“ . Diese 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-Muster>
    </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>
  • Die Zeilen 7–10 definieren ein Servlet, c.a.d, eine Java-Klasse, die Client-Anfragen verarbeiten kann. Eine Anwendung JSF funktioniert wie folgt:

Diese Architektur implementiert das Entwurfsmuster MVC (Model-View-Controller). Wir greifen hier noch einmal auf das zuvor Gesagte zurück. Die Bearbeitung einer Client-Anfrage erfolgt in den folgenden vier Schritten:

1 – Anfrage – Der Browser des Kunden sendet eine Anfrage an den Controller [Faces Servlet]. Dieser empfängt alle Anfragen der Kunden. Er ist das Eingangstor der Anwendung. Er entspricht dem „C“ in MVC,

2 – Verarbeitung – Der Controller C verarbeitet diese Anfrage. Dabei wird er von anwendungsspezifischen Ereignisbehandlungsroutinen der geschriebenen Anwendung [2a] unterstützt. Diese Ereignisbehandlungsroutinen benötigen möglicherweise Unterstützung durch die Geschäftslogik-Schicht [2b]. Sobald die Anfrage des Kunden bearbeitet wurde, kann dies verschiedene Antworten nach sich ziehen. 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 Kunden gesendet werden soll. Die Auswahl der an den Kunden zu sendenden Antwort erfordert mehrere Schritte:

  • Auswahl des Facelets, das die Antwort generieren soll. Dies wird als Ansicht V bezeichnet, das V von MVC. Diese Auswahl hängt in der Regel vom Ergebnis der Ausführung der vom Benutzer angeforderten Aktion ab;
  • diesem Facelet die Daten zur Verfügung stellen, 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 Modell M der Ansicht, das M aus MVC,

Schritt 3 besteht also in der Auswahl einer Ansicht V und der Erstellung des dafür erforderlichen Modells M.

4 – Antwort – Der Controller C fordert das ausgewählte Facelet auf, sich anzuzeigen. Dieses 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 unterschiedlich sein: Es kann sich um einen HTML-Stream, PDF, Excel usw. handeln.

In einem Projekt JSF:

  • ist der Controller C das Servlet [javax.faces.webapp.FacesServlet],
  • die Ansichten V werden durch Seiten implementiert, die die Facelets-Technologie nutzen,
  • Die M-Modelle und die Ereignisbehandler werden durch Java-Klassen implementiert, die oft als „Backing Beans“ oder einfach 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 Tag <servlet-mapping> dient dazu, ein Servlet mit einer vom Client-Browser angeforderten URL zu verknüpfen. Hier wird festgelegt, dass URL-Dateien der Form [/faces/*] vom Servlet mit dem Namen [Faces Servlet] verarbeitet werden sollen. Dieses ist in den Zeilen 7–10 definiert. Da es in der Datei kein weiteres <servlet-mapping>-Tag gibt, bedeutet dies, dass das Servlet [Faces Servlet] nur die URL der Form [/faces/*] verarbeitet. Wir haben gesehen, dass der Anwendungskontext den Namen [/mv-jsf2-01] trägt. Die URL-Dateien der Kunden, die vom Servlet [Faces Servlet] verarbeitet werden, haben daher die Form [http://machine:port/mv-jsf2-01/faces/*]. Die .html- und .jsp-Seiten werden standardmäßig vom Servlet-Container selbst verarbeitet und nicht von einem bestimmten Servlet. Der Servlet-Container weiß nämlich, wie er damit umgehen muss,
  • Zeilen 7–10: Definieren das Servlet [Faces Servlet]. Da alle angenommenen URL-Anfragen an dieses Servlet weitergeleitet werden, ist es der Controller C des Modells MVC,
  • Zeile 10: gibt an, dass das Servlet bereits beim Start des Webservers in den Speicher geladen werden soll. Standardmäßig wird ein Servlet erst beim Eingang der ersten an es gerichteten Anfrage geladen,
  • Zeilen 3–6: Definieren einen Parameter für das Servlet [Faces Servlet]. Der Parameter javax.faces.PROJECT_STAGE legt fest, in welcher Phase sich das ausgeführte Projekt befindet. In der Entwicklungsphase (Development) zeigt das Servlet [Faces Servlet] Fehlermeldungen an, die für die Fehlersuche hilfreich sind. In der Produktionsphase (Production) werden diese Meldungen nicht mehr angezeigt,
  • Zeilen 17–19: Dauer einer Sitzung in Minuten. Ein Client kommuniziert mit der Anwendung über eine Abfolge von Anfrage-/Antwortzyklen. Jeder Zyklus nutzt eine eigene Verbindung TCP-IP, die bei jedem neuen 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 beide Anfragen zum selben Client C gehören. Der Server S verfügt nicht über den Speicher des Clients. Dies liegt am verwendeten Protokoll HTTP (HyperText Transport Protocol): Der Client kommuniziert mit dem Server über eine Abfolge von Zyklen aus Client-Anfrage und Server-Antwort, wobei jedes Mal eine neue Verbindung TCP-IP verwendet wird. Man spricht hier von einem zustandslosen Protokoll. Bei anderen Protokollen, wie beispielsweise FTP (File Transfer Protocol), nutzt der Client C während der gesamten Dauer seines Dialogs 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 erkennen zu können, dass eine Anfrage von einem bestimmten Client stammt, kann der Webserver die Session-Technik nutzen:
    • Bei der ersten Anfrage eines Clients sendet der Server S ihm die erwartete Antwort sowie ein Token, eine zufällige Zeichenfolge, die für diesen Client einzigartig ist;
    • Bei jeder folgenden Anfrage sendet der Client C das empfangene Token an den Server S zurück, wodurch dieser ihn wiedererkennen kann.

Die Anwendung hat nun die Möglichkeit, den Server aufzufordern, Informationen zu einem bestimmten Kunden zu speichern. Man spricht dabei von einer Kundensitzung. Zeile 18 gibt an, dass die Lebensdauer einer Sitzung 30 Minuten beträgt. Das bedeutet: Wenn ein Client C 30 Minuten lang keine neue Anfrage stellt, wird seine Sitzung beendet und die darin enthaltenen Informationen gehen verloren. Bei seiner nächsten Anfrage wird alles so ablaufen, als wäre er ein neuer Client, und es wird eine neue Sitzung gestartet,

  • Zeilen 21–23: Die Liste der Seiten, die angezeigt werden sollen, wenn der Benutzer den Kontext anfordert, ohne eine bestimmte Seite anzugeben, hier zum Beispiel [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. Wenn ja, zeigt er die erste in der Liste gefundene Seite an. Wenn diese nicht existiert, die zweite Seite und so weiter, bis eine vorhandene Seite gefunden wird. Wenn der Client hier URL [http://machine:port/mv-jsf2-01] anfordert, wird ihm URL [http://machine:port/mv-jsf2-01/index.xhtml] bereitgestellt.

2.3.5. Das Projekt ausführen

Wenn man das neue Projekt ausführt, sieht das Ergebnis im Browser wie folgt aus:

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

Man könnte neugierig sein, sich den empfangenen Quellcode [3] anzusehen:

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 erhalten. Alle <h:xx>-Tags aus index.xhtml wurden in ihre Entsprechungen in HTML übersetzt.

2.3.6. Das lokale Maven-Repository

Wir haben bereits erwähnt, dass Maven die für das Projekt erforderlichen Abhängigkeiten herunterlädt und lokal speichert. Dieses lokale Repository kann wie folgt durchsucht werden:

  • In [1] wählen wir die Option [Window / Other / Maven Repository Browser],
  • in [2] öffnet sich eine Registerkarte [Maven Repositories],
  • bei [3] enthält er zwei Zweige, einen für das lokale Repository, den anderen für das zentrale Repository. Letzteres ist riesig. Um dessen Inhalt anzuzeigen, muss der Index [4] aktualisiert werden. Diese Aktualisierung dauert mehrere Dutzend Minuten.
  • in [5], die Bibliotheken des lokalen Repositories,
  • in [6] findet man einen Zweig [istia.st], der dem [groupId] unseres Projekts entspricht,
  • unter [7] greift man auf die Eigenschaften des lokalen Repositorys zu,
  • unter [8] wird der Pfad zum lokalen Repository angezeigt. Es ist hilfreich, diesen zu kennen, da Maven manchmal (selten) nicht mehr die neueste Version des Projekts verwendet. Man nimmt Änderungen vor und stellt fest, dass diese nicht berücksichtigt werden. In diesem Fall kann man den Zweig des lokalen Repositorys, der unserem [groupId] entspricht, manuell löschen. Dadurch wird Maven gezwungen, den Zweig anhand der neuesten Version des Projekts neu zu erstellen.

2.3.7. Ein Artefakt mit Maven suchen

Lernen wir nun, wie man mit Maven nach einem Artefakt sucht. Beginnen wir mit der Liste der aktuellen Abhängigkeiten der Datei [pom.xml]:


<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 die Repositories, in denen sie zu finden sind, zusätzlich zum zentralen Repository, das immer verwendet wird. Wir werden die Abhängigkeiten ändern, um die Bibliotheken in ihrer neuesten Version zu verwenden.

Zunächst entfernen wir die aktuellen Abhängigkeiten [1]. Die Datei [pom.xml] wird daraufhin 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>

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

  • In [1] wird dem Projekt eine Abhängigkeit hinzugefügt,
  • Bei [2] müssen Angaben zum gesuchten Artefakt gemacht werden (groupId, artifactId, Version, Packaging (Typ) und Umfang). Zunächst geben wir [groupId] und [3] ein,
  • bei [4] geben wir [espace] ein, um die Liste der möglichen Artefakte anzuzeigen. Hier sind es [jsf-api] und [jsf-impl]. Wir wählen [jsf-api],
  • bei [5] wählen wir auf die gleiche Weise die aktuellste Version aus. Der Packaging-Typ ist jar.

Wir verfahren so mit allen Artefakten:

In [6] erscheinen die hinzugefügten Abhängigkeiten im Projekt. 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, dass wir die [groupId] des gewünschten Artefakts nicht kennen. Beispielsweise möchten wir Hibernate als ORM (Objekt-Relational-Mapper) verwenden, und das ist alles, was wir wissen. Dann kann man die Website [http://mvnrepository.com/] aufrufen:

In [1] kann man Stichwörter eingeben. Geben wir hibernate ein und starten die Suche.

  • In [2] wählen wir [groupId], org.hibernate sowie [artifactId] und hibernate-core aus,
  • aus [3] wählen wir die Version 4.1.2-Final,
  • aus [4] erhalten wir den Maven-Code, den wir in die Datei [pom.xml] einfügen müssen. Das tun wir.

<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 beginnt daraufhin mit dem Herunterladen der neuen Abhängigkeiten. 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 Datei [pom.xml] beschrieben. Diese Datei wurde gelesen, und Maven hat festgestellt, dass die Datei [artifactId] Abhängigkeiten hat. Diese lädt es ebenfalls herunter. Dies wird für jede heruntergeladene Datei [artifactId] durchgeführt. Letztendlich findet man in der Datei „[6]“ Abhängigkeiten, die man nicht direkt angefordert hatte. Sie werden durch ein anderes Symbol gekennzeichnet als die der Hauptdatei „[artifactId]“.

In diesem Dokument nutzen wir Maven hauptsächlich wegen dieser Eigenschaft. So müssen wir nicht alle Abhängigkeiten einer Bibliothek kennen, die wir verwenden möchten. Wir überlassen es Maven, diese zu verwalten. Außerdem stellt die gemeinsame Nutzung einer [pom.xml]-Datei unter den Entwicklern sicher, dass jeder Entwickler tatsächlich dieselben Bibliotheken verwendet.

In den folgenden Beispielen beschränken wir uns darauf, die verwendete Datei [pom.xml] anzugeben. Der Leser muss diese nur verwenden, um die gleichen Voraussetzungen wie im Dokument zu schaffen. Zudem werden Maven-Projekte von den gängigsten Java-Umgebungen (Eclipse, NetBeans, IntelliJ, JDeveloper) unterstützt. Der Leser kann daher seine bevorzugte Umgebung verwenden, um die Beispiele zu testen.

2.4. Beispiel „ “ mv-jsf2-02: Ereignis-Handler – Internationalisierung – Navigation zwischen Seiten

2.4.1. Die Anwendung

Die Anwendung sieht wie folgt aus:

  • in [1], die Startseite der Anwendung,
  • in [2], zwei Links zum Ändern der Sprache der Anwendungsseiten,
  • zu [3], ein Navigationslink zu einer anderen Seite,
  • wenn man auf [3] klickt, wird die Seite [4] angezeigt,
  • über den Link [5] gelangt man zurück zur Startseite.
  • Auf der Startseite [1] ermöglichen die Links [2] die Sprachumschaltung,
  • unter [3] die Startseite auf Englisch.

2.4.2. Das NetBeans-Projekt

Es wird ein neues Webprojekt erstellt, wie in Abschnitt 2.3.1 beschrieben. Es erhält den Namen mv-jsf2-02:

  • in [1], das generierte Projekt,
  • in [2] wurden das Paket [istia.st.mvjsf202] und die Datei [index.jsp] gelöscht,
  • in [3] wurden mithilfe der folgenden Datei [pom.xml] Maven-Abhängigkeiten 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 Framework JSF. Kopieren Sie einfach die obigen Zeilen in die Datei [pom.xml] und ersetzen Sie damit die alten Abhängigkeiten.

  • in [4, 5]: Man erstellt einen Ordner [src / main / resources] auf der Registerkarte [Files],
  • in [6], auf der Registerkarte [Projects], wodurch der Zweig [Other Sources] erstellt wurde.

Wir haben nun ein Projekt mit dem Namen JSF. Darin werden wir verschiedene Dateitypen erstellen:

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

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

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

Die Datei „[web.xml] “ konfiguriert die Anwendung „JSF“. Sie lautet wie folgt:


<?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 erläutert. Hier noch einmal die wichtigsten Eigenschaften:

  • Alle URL-Dateien 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] 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">
  <h:head>
    <title>Facelet Title</title>
  </h:head>
  <h:body>
    Hello from Facelets
  </h:body>
</html>

Diese Datei haben wir bereits in Abschnitt 2.3.4 kennengelernt.

Erstellen wir nun eine Java-Klasse:

  • In [1] erstellen wir eine Java-Klasse im Zweig [Source Packages],
  • in [2], man gibt ihr einen Namen und fügt sie in das Paket [3] ein,
  • in [4] erscheint die erstellte Klasse im Projekt.

Der Code der erstellten 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 {
  
}

Schließlich erstellen wir eine Meldungsdatei:

  • in [1], Erstellung einer Datei [Properties],
  • in [2] geben wir den Dateinamen an und in [3] den Ordner,
  • in [4] wurde die Datei [messages.properties] erstellt.

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

  • In [1] erstellen wir die Konfigurationsdatei JSF,
  • in [2] geben wir den Namen ein und in [3] den 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 werden ihn später vervollständigen.

Wir verfügen nun über alle Elemente, um ein Projekt JSF zu erstellen. In den folgenden Beispielen stellen wir das vollständige Projekt JSF vor und erläutern anschließend die einzelnen Elemente im Detail. Wir stellen nun ein Projekt vor, um die folgenden Konzepte zu erklären:

  • Verwaltung von Formularereignissen,
  • der Internationalisierung der Seiten einer Website JSF,
  • der Navigation zwischen Seiten.

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

  • in [1], der Konfigurationsdateien des Projekts JSF,
  • in [2], die Seiten JSF des Projekts,
  • in [3] die einzige Java-Klasse,
  • in [4] die Meldungsdateien.

2.4.3. Die Seite [index.xhtml]

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

Der Code, der diese Seite erzeugt, 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 Namensräume bzw. Tag-Bibliotheken. Tags mit dem Präfix „h“ sind HTML-Tags, während Tags mit dem Präfix „f“ spezifische Tags von JSF sind,
  • Zeile 10: Das Tag <f:view> dient dazu, den Code abzugrenzen, den die Engine JSF verarbeiten soll, d. h. den Code, in dem die Tags <f:xx> vorkommen. Mit dem Attribut „locale“ kann eine Anzeigesprache für die Seite festgelegt werden. Hier verwenden wir zwei Sprachen: Englisch und Französisch. Der Wert des Attributs „local“ wird in Form eines EL-Ausdrucks (Expression Language) #{Ausdruck} angegeben. Die Form des Ausdrucks kann unterschiedlich sein. Meistens werden wir ihn in der Form „bean['clé']“ oder „bean.champ“ ausdrücken. In unseren Beispielen ist „bean“ entweder eine Java-Klasse oder eine Meldungsdatei. 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. Man kann nun Annotationen verwenden, die eine Java-Klasse zu einem für JSF 2 bekannten Bean machen. Die Nachrichtendatei muss ihrerseits in der Konfigurationsdatei [faces-config.xml] deklariert werden.

2.4.4. Die Bean [changeLocale]

Im Ausdruck EL #{changeLocale.locale}:

  • changeLocale ist der Name einer Bean, in diesem Fall die Java-Klasse ChangeLocale,
  • „locale“ ist ein Feld der Klasse ChangeLocale. Der Ausdruck wird von [ChangeLocale].getLocale() ausgewertet. Im Allgemeinen wird der Ausdruck #{bean.champ} als [Bean] ausgewertet.getChamp(), wobei [Bean] eine Instanz der Java-Klasse ist, der der Name bean und getChamp zugewiesen wurde, der zum Feld champ der Bean gehörende Getter.

Die Klasse ChangeLocale lautet wie folgt:


package utils;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // die Sprache der Seiten
  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 "). Fehlt das Attribut name, wird der Name der Klasse verwendet, wobei das erste Zeichen in Kleinbuchstaben umgewandelt wird. Der Name der Bean ChangeLocale lautet somit changeLocale. Es ist zu beachten, dass die Annotation ManagedBean zum Paket javax.faces.bean.ManagedBean gehört und nicht zum Paket javax.annotations.ManagedBean.
  • Zeile 8: Die Annotation SessionScoped legt den Gültigkeitsbereich des Beans fest. Davon gibt es mehrere. Wir werden üblicherweise die folgenden drei verwenden:
    • RequestScoped: Die Lebensdauer des Beans entspricht dem Zyklus „Browseranfrage / Serverantwort“. Wenn dieses Bean zur Bearbeitung einer neuen Anfrage desselben oder eines anderen Browsers erneut benötigt wird, wird es erneut instanziiert,
    • SessionScoped: Die Lebensdauer der Bean entspricht der Dauer der Sitzung eines bestimmten Clients. Die Bean wird ursprünglich für die Bearbeitung einer der Anfragen dieses Clients erstellt. Anschließend verbleibt sie im Speicher innerhalb der Sitzung dieses Clients. Eine solche Bean speichert in der Regel Daten, die für einen bestimmten Client spezifisch sind. Sie wird gelöscht, sobald die Sitzung des Clients beendet wird,
    • ApplicationScoped: Die Lebensdauer der Bean entspricht der Lebensdauer der Anwendung selbst. Eine Bean mit dieser Lebensdauer wird meist von allen Clients der Anwendung gemeinsam genutzt. Sie wird in der Regel zu Beginn 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 Paket JSF 2. Dazu müssen wir die Datei [WEB-INF / beans.xml] erstellen:

  

Diese Datei wird automatisch von NetBeans generiert, wenn das Paket [javax.enterprise.context.SessionScoped] importiert wird. Ihr 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>

Abgesehen vom Stamm-Tag <beans> ist die Datei leer. Das reicht aus. Es reicht aus, dass sie vorhanden ist.

Abschließend sei angemerkt, dass die Klasse [ChangeLocale] die Schnittstelle [Serializable] implementiert. Dies ist für Beans mit dem Geltungsbereich Session zwingend erforderlich, da der Webserver diese möglicherweise in Dateien serialisieren muss. Auf die Bean [ChangeLocale] werden wir später noch 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 Tag <h:outputText> zeigt den Wert eines Ausdrucks EL #{msg['welcome.titre']} in der Form #{bean['champ']} an. „bean“ ist entweder der Name einer Java-Klasse oder der einer Meldungsdatei. In diesem Fall handelt es sich um den Namen einer Meldungsdatei. Diese muss in der Konfigurationsdatei [faces-config.xml] deklariert sein. Die Bean „msg“ 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 Tag <application> dient zur Konfiguration der Anwendung JSF,
  • Zeilen 12–17: Das Tag <resource-bundle> dient dazu, Ressourcen für die Anwendung zu definieren, in diesem Fall eine Meldungsdatei,
  • Zeilen 13–15: Das Tag <base-name> definiert den Namen der Meldungsdatei,
  • Zeile 14: Die Datei wird messages[_CodeLangue][_CodePays].properties“ heißen. Das Tag <base-name> definiert nur den ersten Teil des Namens. Der Rest wird implizit ergänzt. Es können mehrere Meldungsdateien vorhanden sein, eine pro Sprache:
  • In [1] sind vier Nachrichtendateien zu sehen, die dem in [faces-config.xml] definierten Basisnamen „messages“ entsprechen,
    • messages_fr.properties: enthält die Meldungen auf Französisch (Code fr);
    • messages_en.properties: enthält die Meldungen auf Englisch (Code „en“);
    • messages_es_ES.properties: enthält die Meldungen auf Spanisch (Code „es“) aus Spanien (Code ES). Es gibt weitere Varianten des Spanischen, beispielsweise die aus Bolivien (es_BO);
    • messages.properties: Wird vom Server verwendet, wenn für die Sprache des Rechners, auf dem er ausgeführt wird, keine zugehörige Meldungsdatei vorhanden ist. Dies wäre beispielsweise der Fall, wenn die Anwendung auf einem Rechner in Deutschland ausgeführt würde, wo die Standardsprache Deutsch (de) ist. Da keine Datei mit dem Namen [messages_de.properties] existiert, würde die Anwendung die Datei [messages.properties] verwenden,
  • in [2]: Die Sprachcodes unterliegen einem internationalen Standard,
  • in [3]: dasselbe gilt für die Ländercodes.

Der Name der Meldungsdatei wird in Zeile 14 definiert. Er wird in der Datei „Classpath“ des Projekts gesucht. Befindet er sich in einem Paket, muss dieses in Zeile 14 definiert werden, zum Beispiel ressources.messages, wenn sich die Datei [messages.properties] im Ordner [ressources] des Classpath 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 einem bis drei Codes identifiziert: [codeLangue_codePays_codeVariante]. In [1] wurde nur der Code [codeLangue] verwendet: „en“ für Englisch, „fr“ für Französisch. Jede Version ist Gegenstand einer separaten Datei im Dateisystem.

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


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 hingegen 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 Client-Browser die Wahl zwischen Seiten auf Französisch und Seiten auf Englisch.

Kommen wir zurück zur Datei [faces-config.xml], die die Nachrichtendatei deklariert:


...

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

Zeile 8 gibt an, dass eine Zeile der Meldungsdatei in den Seiten JSF über den Bezeichner „msg“ referenziert wird. Dieser Bezeichner wird in der untersuchten 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 Tag <h:outputText> in Zeile 8 zeigt den Wert der Nachricht (Vorhandensein des Bezeichners „msg“) mit dem Schlüssel welcome.titre an. Diese Meldung wird in der Datei [messages.properties] der aktuell aktiven Sprache gesucht und gefunden. Zum Beispiel für Französisch:


welcome.titre=Tutoriel JSF (JavaServer Faces)

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


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

Dieser Mechanismus der Nachrichten-Dateien ermöglicht es, die Sprache der Seiten eines Projekts JSF einfach zu ändern. Man spricht von der Internationalisierung des Projekts oder häufiger von 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

Sehen 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 Tag <h:form> leitet ein Formular ein. Ein Formular besteht in der Regel aus:
    • Tags für Eingabefelder (Text, Optionsfelder, Kontrollkästchen, Auswahllisten usw.);
    • Tags zur Formularvalidierung (Schaltflächen, Links). Über eine Schaltfläche oder einen Link sendet der Benutzer seine Eingaben an den Server, der diese verarbeitet,

Jedes JSF-Tag kann durch ein id-Attribut identifiziert werden. Meistens kann man darauf verzichten, und genau das wurde bei den meisten hier verwendeten JSF-Tags getan. Dennoch ist dieses Attribut in bestimmten Fällen nützlich. Zeile 17: Das Formular wird durch die Formular-ID identifiziert. In diesem Beispiel wird die Formular-ID nicht verwendet und hätte daher weggelassen werden können.

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

In beiden Fällen wird die Seite JSF angezeigt, sobald die durch das Attribut „action“ 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 Meldungsdatei herangezogen, 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 Übersetzung HTML dieses Tags JSF lautet wie folgt:


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

was zu folgender visueller Darstellung führt:

Zu beachten ist das Attribut „onclick“ des Tags HTML <a>. Wenn der Benutzer auf den Link [Français] klickt, wird JavaScript-Code ausgeführt. Dieser ist in die Seite eingebettet, die der Browser empfangen hat, und wird vom Browser ausgeführt. JavaScript-Code wird häufig in den Technologien JSF und AJAX (Asynchronous JavaScript and XML) verwendet. Er dient in der Regel dazu, die Benutzerfreundlichkeit und Reaktionsgeschwindigkeit von Webanwendungen zu verbessern. Meistens wird er automatisch von Software-Tools generiert, sodass es nicht notwendig ist, ihn zu verstehen. Manchmal kann es jedoch vorkommen, dass ein Entwickler JavaScript-Code in seine JSF-Seiten einfügen muss. In diesem Fall sind JavaScript-Kenntnisse erforderlich.

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

  • Der JavaScript-Code verwendet die Formular-ID, die wir dem Tag JSF <h:form> zugewiesen haben,
  • JSF generiert automatische IDs für alle Tags, bei denen das id-Attribut nicht definiert wurde. Ein Beispiel dafür sehen wir hier: j_idt8. Die Vergabe eindeutiger Identifikatoren für die Tags ermöglicht ein besseres Verständnis des generierten JavaScript-Codes, falls dies erforderlich wird. Dies ist insbesondere dann der Fall, wenn der Entwickler selbst JavaScript-Code hinzufügen muss, der die Komponenten der Seite manipuliert. In diesem Fall muss er die ID-Identifikatoren seiner Komponenten kennen.

Was passiert, wenn der Benutzer auf den Link [Français] auf der obigen Seite klickt? Betrachten wir die Architektur einer Anwendung JSF:

Der Controller [Faces Servlet] empfängt die Anfrage des Client-Browsers in der folgenden Form: HTTP:

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. Das ist immer so: Die Eingaben in einem Formular JSF, das ursprünglich mit den URL und URLFormulaire abgerufen wurde, werden an dasselbe URL gesendet. Der Browser hat zwei Möglichkeiten, die eingegebenen Werte zu senden: GET und POST. Bei der Methode GET werden die eingegebenen Werte vom Browser an das angeforderte Formular 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

Bei der hier verwendeten Methode POST sendet der Browser die eingegebenen Werte über Zeile 6 an den Server.

  • Zeile 3: gibt die Kodierungsform der Formularwerte an,
  • Zeile 4: gibt die Größe in Byte von Zeile 6 an,
  • Zeile 5: leere Zeile, die das Ende der Kopfzeilen „HTTP“ und den Beginn der 126 Byte mit den Formularwerten markiert,
  • Zeile 6: Die Formularwerte in der Form element1=Wert1&element2=Wert2& ..., wobei das Kodierungsformat durch Zeile 3 definiert ist. Bei diesem Kodierungsformat werden bestimmte Zeichen durch ihren Hexadezimalwert ersetzt. Dies ist beim letzten Element der Fall:

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

wobei %3A das Zeichen : darstellt. Es ist also die Zeichenkette „Formular:j_idt8=Formular:j_idt8“, die an den Server gesendet wird. Vielleicht erinnern Sie sich, dass wir die Kennung j_idt8 bereits kennengelernt haben, als wir den für das Tag


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

Er wurde automatisch von JSF generiert. Wichtig ist hier, dass das Vorhandensein dieser Kennung in der vom Client-Browser gesendeten Wertfolge es JSF ermöglicht, zu erkennen, dass der Link [Français] angeklickt wurde. Es nutzt dann das oben genannte „action“-Attribut, um zu entscheiden, wie die empfangene Zeichenfolge verarbeitet werden soll. Das Attribut „action="#{changeLocale.setFrenchLocale}"“ weist JSF an, dass die Anfrage des Clients durch die Methode [setFrenchLocale] eines Objekts namens changeLocale verarbeitet werden soll. Zur Erinnerung: Diese Bean wurde durch Annotationen in der Java-Klasse [ChangeLocale] definiert:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

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

Kommen wir zurück zur Anfrage des Browsers:

und zum Tag <h:commandLink>, das den Link [Français] generiert hat, auf den geklickt wurde:


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

Der Controller leitet die Anfrage des Browsers an den Ereignishandler weiter, der durch das Attribut „action“ des Tags <h:commandLink> definiert ist. Der Ereignishandler M, auf den das Attribut „action“ eines Befehls <h:commandLink> verweist, muss die folgende Signatur haben:

public String M();
  • Er erhält keine Parameter. Wir werden sehen, dass er dennoch Zugriff auf die Anfrage des Clients haben kann;
  • er muss ein Ergebnis C vom Typ String zurückgeben. Diese Zeichenkette C kann entweder
    • entweder der Name einer Seite JSF des Projekts;
    • entweder ein Name, der in den Navigationsregeln der Datei [faces-config.xml] definiert und einer Seite JSF des Projekts zugeordnet ist;
    • entweder ein Null-Zeiger, wenn der Client-Browser die Seite nicht wechseln soll,

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

Im Tag


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

des Ereignis-Handlers für den Klick auf den Link [Français] ist die Methode [changeLocale.setFrenchLocale], wobei changeLocale eine Instanz der bereits behandelten 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{
  // die Sprachversion der Seiten
  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 weist tatsächlich die Signatur eines Ereignishandlers auf. Erinnern wir uns daran, dass der Ereignishandler die Anfrage des Clients verarbeiten muss. Da er keine Parameter erhält, wie kann er dann auf diese zugreifen? Es gibt verschiedene Möglichkeiten:

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

Wenn die vom Client-Browser gesendeten Parameter (POST) 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
Formular
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 Ereignismanager locale.setFrenchLocale erwartet? Er soll die von der Anwendung verwendete Sprache festlegen. Im Java-Jargon nennt man dies die „Lokalisierung“ der Anwendung. Diese Lokalisierung wird vom Tag <f:view> der Seite JSF [index.xhtml] verwendet:


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

Um die Seite auf Französisch umzustellen, muss das Attribut „locale“ lediglich den Wert „fr“ haben. Um sie auf Englisch umzustellen, muss man ihm den Wert „en“ zuweisen. Der Wert des Attributs „locale“ wird durch den Ausdruck [ChangeLocale].getLocale() ermittelt. Dieser Ausdruck liefert den Wert des Feldes locale der Klasse [ChangeLocale]. Daraus lässt sich der Code der Methode [ChangeLocale].setFrenchLocale() ableiten, die die Seiten auf Französisch umstellen soll:


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

Wir haben erläutert, dass ein Ereignis-Handler eine C-Zeichenkette zurückgeben muss, die von [Faces Servlet] verwendet wird, um die Seite JSF zu finden, die als Antwort an den Client-Browser gesendet werden soll. Wenn die zurückzugebende Seite dieselbe ist wie die gerade bearbeitete, 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 Anfrageverarbeitung zurück:

Der Ereignis-Handler changeLocale.setFrenchLocale wurde ausgeführt und hat den Wert null an den Controller [Faces Servlet] zurückgegeben. Dieser wird daher die Seite [index.xhtml] erneut anzeigen. Sehen wir uns diese 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 Meldungsdateien [messages.properties] verwendet. Es wird diejenige verwendet, die der „Lokalisierung“ der Seite entspricht (Zeile 6). Da der Ereignis-Handler changeLocale.setFrenchLocale diese Lokalisierung auf fr festlegt, wird die Datei [messages_fr.properties] verwendet. Ein Klick auf den Link [Anglais] (Zeile 14) ändert die Lokalisierung auf en (siehe Methode changeLocale.setEnglishLocale). Dann wird die Datei „[messages_en.properties]“ verwendet und die Seite erscheint auf Englisch:

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


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

und somit wird die Methode [ChangeLocale].getLocale() erneut ausgeführt. Da wir unserer Bean den Gültigkeitsbereich „Session“ zugewiesen haben:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

bleibt die bei einer Anfrage vorgenommene Lokalisierung für nachfolgende Anfragen erhalten.

Es bleibt noch ein letztes Element der Seite [index.xhtml] zu untersuchen:


<?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 Tag <h:commandLink> in Zeile 17 hat ein „action“-Attribut, dessen Wert eine Zeichenkette ist. In diesem Fall wird kein Ereignis-Handler zur Verarbeitung der Seite aufgerufen. Es wird sofort zur Seite [page1.xhtml] weitergeleitet. Sehen wir uns die Funktionsweise der Anwendung in diesem Anwendungsfall an:

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


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

Dem Link ist kein Ereignis-Handler zugeordnet. Der Controller [Faces Servlet] springt sofort zum obigen Schritt [3] und zeigt die Seite [page1.xhtml] an:

2.4.7. Die Seite JSF [page1.xhtml]

Die Seite [page1.xhtml] sendet den folgenden Datenstrom an den Client-Browser:

 

Der Code, der diese Seite erzeugt, 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 wird die Zuordnung zwischen dem Code JSF und der an den Client-Browser gesendeten Seite herstellen. Der Link zurück zur Startseite:


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

ruft die Seite [index.xhtml] auf.

2.4.8. Ausführung des Projekts

Unser Projekt ist nun vollständig. Wir können es erstellen (Clean and Build):

  • Durch den Build des Projekts wird auf der Registerkarte [Files] der Ordner [target] erstellt. Darin befindet sich das Projektarchiv [mv-jsf2-02-1.0-SNAPSHOT.war]. Dieses Archiv wird auf dem Server bereitgestellt,
  • in [WEB-INF / classes] und [2] befinden sich die kompilierten Klassen aus dem Projektordner [Source Packages] sowie die Dateien, die sich im Zweig [Other Sources] befanden, hier die Meldungsdateien,
  • in [WEB-INF / lib] [3] befinden sich die Bibliotheken des Projekts,
  • im Stammverzeichnis von [WEB-INF] und [4] befinden sich die Konfigurationsdateien des Projekts,
  • Im Stammverzeichnis des Archivs [5] befinden sich die Seiten JSF, die sich im Zweig [Web Pages] des Projekts befanden;
  • Sobald das Projekt erstellt ist, kann es ausgeführt werden: [6]. Es wird gemäß seiner Ausführungskonfiguration [7] ausgeführt,
  • der Tomcat-Server wird gestartet, falls er nicht bereits lief ([8]),
  • wird das Archiv [mv-jsf2-02-1.0-SNAPSHOT.war] auf den Server geladen. Dies wird als Bereitstellung des Projekts auf dem Anwendungsserver bezeichnet,
  • bei [9] wird bei der Ausführung der Start eines Browsers angefordert. Dieser fordert den Anwendungskontext [10], c.a.d an. URL und [http://localhost:8080/mv-jsf2-02]. Gemäß den Regeln der Datei [web.xml] (siehe Seite 44) wird die Datei [faces/index.xhtml] an den Client-Browser ausgeliefert. Da die Datei URL die Form [/faces/*] hat, wird sie vom Controller [Faces Servlet] verarbeitet (siehe [web.xml], Seite 44). Dieser verarbeitet die Seite und sendet den folgenden HTML-Datenstrom:
 
  • Der Controller [Faces Servlet] verarbeitet dann die Ereignisse, die auf dieser Seite auftreten.

2.4.9. Die Konfigurationsdatei [faces-config.xml]

Wir haben die folgende Datei „[faces-config.xml]“ 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 Minimaldatei für eine internationalisierte JSF 2-Anwendung. Wir haben hier neue Möglichkeiten von JSF 2 gegenüber JSF 1 genutzt:

  • Deklaration von Beans und deren Gültigkeitsbereich mit den Annotationen @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
  • zwischen Seiten navigieren, wobei die Namen der Seiten XHTML ohne ihr xhtml-Suffix als Navigationsschlüssel verwendet werden.

Möglicherweise möchte man diese Möglichkeiten nicht nutzen und diese Elemente des Projekts JSF in [faces-config.xml] ebenso 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">
<!-- Anwendung -->
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
  
  <!-- verwaltete 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 Beans changeLocale:
    • Zeile 21: Name des Beans;
    • Zeile 22: Vollständiger Name der mit dem Bean verknüpften 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 wurde darauf verzichtet;
    • Zeile 29: Die Seite, von der aus die Navigation erfolgt (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.

Die Navigationsregeln lassen sich anschaulicher darstellen. Beim Bearbeiten der Datei „[faces-config.xml]“ kann man die Registerkarte „[PageFlow]“ verwenden:

 

Nehmen wir an, wir verwenden die zuvor genannte Datei „[faces-config.xml]“. Wie würde sich unsere Anwendung verändern?

  • In der Klasse „[ChangeLocale]“ würden die Annotationen „@ManagedBean“ und „@SessionScoped“ verschwinden, da die Bean nun in „[faces-config]“ deklariert ist,
  • die Navigation von [index.xhtml] zu [page1.xhtml] über einen Link würde dann 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 dann wie folgt aussehen:

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

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

  • die Methoden setFrenchLocale und setEnglishLocale, die einen Navigationsschlüssel zurückgeben müssen, müssen nicht geändert werden, da sie bisher „null“ zurückgaben, um anzuzeigen, dass man auf derselben Seite bleibt.

2.4.10. Fazit

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

Dieses Projekt umfasst die folgende Architektur:

In jedem Projekt JSF finden wir folgende Elemente:

  • Seiten JSF [A], die vom Controller [Faces Servlet] [3] an die Client-Browser gesendet werden [4],
  • Meldungsdateien [C], mit denen die Sprache der Seiten JSF geändert werden kann,
  • Java-Klassen [B], die Ereignisse verarbeiten, die im Client-Browser auftreten [2a, 2b], und/oder als Vorlagen für die Seiten dienen JSF [3]. In den meisten Fällen werden die Schichten [métier] und [DAO] separat entwickelt und getestet. Die Schicht [web] wird dann mit einer fiktiven Schicht [métier] getestet. Sind die Schichten [métier] und [DAO] verfügbar, wird meist mit deren .jar-Archiven gearbeitet.
  • die 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. Gleiches gilt für [faces-config], wo wir stets die vereinfachte Version verwenden werden.

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

Ab sofort werden wir den Aufbau des Projekts nicht mehr zeigen. Wir stellen fertige Projekte vor und erklären deren Funktionsweise. Der Leser kann alle Beispiele auf der Website dieses Dokuments abrufen (siehe Abschnitt 1.2).

2.5.1. Die Anwendung

Die Anwendung verfügt über eine einzige Ansicht:

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

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

2.5.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung sieht wie folgt aus:

  • in [1], die Konfigurationsdateien des Projekts JSF,
  • 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 Anwendungsmeldungsdatei in zwei Sprachen: Französisch und Englisch.

2.5.3. Die Datei [pom.xml]

Wir stellen hier nur die Abhängigkeiten vor:


    <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 Projekt JSF erforderlichen Abhängigkeiten. In den folgenden Beispielen wird diese Datei nur dann vorgestellt, 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 Servlet [Faces Servlet]. Er sorgt dafür, dass Kommentare in einem Facelet wie dem folgenden:

        <!-- Sprachen -->

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

  • Zeilen 3–6: Ein Parameter für das Servlet [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 die Meldungsdatei der Anwendung.

2.5.6. Die Meldungsdatei [messages.properties]

Die Meldungsdateien (siehe [5] im Screenshot des Projekts) 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. Die Vorlage [Form.java] der Seite [index.xhtml]

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


<!-- Zeile 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}"/>

Bei der ersten Aufrufe der Seite [index.xhtml] generiert der obige Code Zeile 2 der Eingabetabelle:

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

Die Zeilen 5 und 7 verwenden einen Ausdruck EL, der die in der Klasse [Form.java] definierte Form-Bean wie folgt einbezieht:


package forms;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;


@ManagedBean
@RequestScoped
public class Form {
  • In Zeile 7 wird ein namenloser Bean definiert. Dieser entspricht somit dem Namen der Klasse, der mit einem Kleinbuchstaben beginnt: form,
  • Die Bean hat den Geltungsbereich „request“. Das bedeutet, dass sie in einem Zyklus aus Client-Anfrage und Server-Antwort instanziiert wird, sobald die Anfrage dies erfordert, und gelöscht wird, sobald die Antwort an den Client zurückgegeben wurde.

Im folgenden Code der Seite [index.xhtml]:


<!-- Zeile 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}"/>

Die Zeilen 5 und 7 verwenden den Wert inputText des Form-Beans. Um die Zusammenhänge zwischen einer Seite P und ihrer Vorlage M zu verstehen, muss man auf den Zyklus „ Kundenanfrage/Serverantwort“ zurückkommen, der für eine Webanwendung charakteristisch ist:

Man muss 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 der Seite P ausgelöst hat und dieses vom Controller [Faces Servlet] verarbeitet wird (Schritt 1).

Man kann diese beiden Fälle unterscheiden, indem man sie aus der Perspektive des Browsers betrachtet:

  1. Bei der ersten Anforderung der Seite führt der Browser eine Operation GET auf die URL der Seite durch,
  2. beim Absenden der auf der Seite eingegebenen Werte führt der Browser eine Operation POST auf dem URL der Seite durch.

In beiden Fällen wird dieselbe URL angefordert. Je nach Art der Anfrage des Browsers (GET oder POST) wird die Bearbeitung der Anfrage unterschiedlich ausfallen.

[cas 1 – demande initiale de la page P]

Der Browser fordert die Seite URL mit einem GET an. Der Controller [Faces Servlet] springt direkt zum Schritt [4] der Antwortdarstellung, und die Seite [index.xhtml] wird an den Client gesendet. Der Controller JSF fordert jedes Tag der Seite auf, sich anzuzeigen. Nehmen wir als Beispiel Zeile 5 des Codes von [index.xhtml]:


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

Das Tag JSF <h:inputText value="Wert"/> erzeugt das Tag HTML <input type="text" value="Wert"/>. 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,
  • der Ausdruck #{form.inputText} wird durch Aufruf der Methode form.getInputText() ausgewertet,
  • der Text <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="text" /> wird in den Datenstrom HTML eingefügt, der an den Client gesendet wird, sofern die Methode form.getInputText() die Zeichenfolge „text“ zurückgegeben hat. JSF weist zudem der in den Datenfluss eingefügten Komponente HTML einen Namen (name) zu. Dieser Name wird aus den ID-Kennungen der analysierten Komponente JSF und denen ihrer übergeordneten Komponenten gebildet, in diesem Fall dem Tag <h:form id="formulaire"/>.

Es ist zu beachten, dass, wenn auf einer Seite P der Ausdruck #{M.champ} verwendet wird, wobei M die Modell-Bean der Seite P ist, diese über die öffentliche Methode getChamp() verfügen muss. Der von dieser Methode zurückgegebene Typ muss in den Typ „String“ konvertiert werden können. Ein mögliches und häufig verwendetes Modell M ist das folgende:

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

wobei T ein Typ ist, der in den Typ String konvertiert werden kann, gegebenenfalls mithilfe einer Methode toString.

Ebenfalls im Fall der Anzeige der Seite P erfolgt die Verarbeitung der Zeile:


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

verläuft analog, und es wird der folgende Datenstrom HTML erstellt:

texte

Serverintern wird die Seite P als Komponentenbaum dargestellt, der den Tag-Baum der an den Client gesendeten Seite widerspiegelt. Wir bezeichnen diesen Baum als Ansicht oder als Seiten . Dieser Zustand wird gespeichert. Dies kann auf zwei Arten geschehen, je nach einer Konfiguration in der Anwendungsdatei [web.xml]:


<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 Controller [Faces Servlet]. Dieser kann über verschiedene <context-param>-Tags konfiguriert werden, darunter das in den Zeilen 3–6, das angibt, dass der Zustand einer Seite auf dem Client (dem Browser) gespeichert werden soll. Der andere mögliche Wert, in Zeile 5, ist „server“, um eine Speicherung auf dem Server anzugeben. Dies ist der Standardwert.

Wenn der Status einer Seite auf dem Client gespeichert wird, fügt der Controller JSF jeder von ihm gesendeten HTML-Seite ein verstecktes Feld hinzu, dessen Wert dem aktuellen Status der Seite entspricht. Dieses versteckte Feld hat 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. Wichtig zu verstehen ist, dass dieses versteckte Feld Teil des Formulars der Seite ist und somit zu den Werten gehört, die der Browser bei der Übermittlung des Formulars sendet. Anhand dieses versteckten Feldes kann der Controller JSF die Ansicht so wiederherstellen, wie sie an den Client gesendet wurde.

Wenn der Status einer Seite auf dem Server gespeichert wird, wird der Status der an den Client gesendeten Seite in dessen Sitzung gespeichert. Wenn der Browser des Clients die im Formular eingegebenen Werte per POST übermittelt, sendet er gleichzeitig sein Sitzungstoken mit. Anhand dieses Tokens kann der Controller JSF den Zustand der an den Kunden gesendeten Seite wiederherstellen.

Der Zustand einer Seite JSF kann mehrere hundert Byte Speicherplatz beanspruchen. Da dieser Zustand für jeden Nutzer der Anwendung aufrechterhalten wird, kann es bei einer großen Anzahl von Nutzern zu Speicherproblemen kommen. Aus diesem Grund haben wir uns hier dafür entschieden, den Zustand der Seite auf dem Client zu speichern (siehe [web.xml], Abschnitt 2.5.4, Seite 66).

[cas 2 – traitement de la page P]

Wir befinden uns im oben genannten Schritt [1], in dem der Controller [Faces Servlet] eine Anfrage POST vom Client-Browser erhält, an den er zuvor die Seite [index.xhtml] gesendet hat. Hier handelt es sich um die Verarbeitung eines Seitenereignisses. Es werden mehrere Schritte durchlaufen, bevor das Ereignis in [2a] verarbeitet werden kann. Der Verarbeitungszyklus einer Anfrage POST durch den Controller JSF sieht wie folgt aus:

Image

  • In [A] wird mithilfe des versteckten Felds javax.faces.ViewState die ursprünglich an den Client-Browser gesendete Ansicht rekonstruiert. Dabei erhalten die Komponenten der Seite wieder den Wert, den sie in der gesendeten Seite hatten. Unsere Komponente inputText erhält wieder ihren Wert „text“,
  • in [B] werden die vom Client-Browser übermittelten Werte verwendet, um die Komponenten der Ansicht zu aktualisieren. Wenn also der Benutzer im Eingabefeld HTML mit dem Namen 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 Seite, wie sie an den Browser gesendet wurde,
  • in [C] werden die übermittelten Werte überprüft. Nehmen wir an, die vorhergehende Komponente inputText sei das Eingabefeld für das Alter. Der eingegebene Wert muss eine ganze Zahl sein. Die vom Browser übermittelten Werte sind immer vom Typ String. Ihr endgültiger Typ im der Seite P zugeordneten M-Modell kann jedoch ein ganz anderer sein. Es findet dann eine Konvertierung von einem Typ String in einen anderen Typ T statt. Diese Konvertierung kann fehlschlagen. In diesem Fall wird der Anfrage-/Antwortzyklus beendet und die in [B] erstellte Seite P wird an den Client-Browser zurückgesendet, gegebenenfalls zusammen mit Fehlermeldungen, sofern der Autor der Seite P diese vorgesehen hat. Es ist zu beachten, dass der Benutzer die Seite so vorfindet, 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 mit den vom Benutzer eingegebenen Werten selbst neu erstellen. Der Wert einer Komponente kann zudem einem Validierungsprozess unterzogen werden. Nehmen wir erneut das Beispiel der Komponente inputText, bei der es sich um ein Eingabefeld für das Alter handelt: Der eingegebene Wert muss nicht nur eine ganze Zahl sein, sondern eine ganze Zahl, die in einem bestimmten Intervall [1,N] liegt. Selbst wenn der eingegebene Wert die Konvertierungsphase besteht, kann es sein, dass er die Validierungsphase nicht besteht. Auch in diesem Fall ist der Anfrage-/Antwortzyklus abgeschlossen und die in [B] erstellte Seite P wird an den Client-Browser zurückgesendet,
  • in [D]. Wenn alle Komponenten der Seite P die Konvertierungs- und Validierungsphase bestehen, werden ihre Werte dem Modell M der Seite P zugewiesen. Wenn der Wert des Eingabefelds, das aus dem folgenden Tag generiert wurde:

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

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

  • Sobald das Modell M der Seite P durch die übermittelten Werte aktualisiert wurde, kann das Ereignis, das den Aufruf von POST auf der Seite P ausgelöst hat, verarbeitet werden. Dies ist der Schritt [E]. Es ist zu beachten, dass der Handler dieses Ereignisses, sofern er zur Bean M gehört, Zugriff auf die Werte des Formulars P hat, die in den Feldern dieser Bean gespeichert wurden.
  • Der Schritt [E] übergibt dem Controller JSF einen Navigationsschlüssel. In unseren Beispielen ist dies immer der Name der anzuzeigenden Seite XHTML ohne das Suffix .xhtml. Dies ist der Schritt [F]. Eine andere Möglichkeit besteht darin, einen Navigationsschlüssel zurückzugeben, der in der Datei [faces-config.xml] gesucht wird. Diesen Fall haben wir beschrieben.

Aus dem Vorstehenden lässt sich Folgendes ableiten:

  • Eine Seite P zeigt die Felder C ihres Modells M mithilfe der Methoden [M].getC() an,
  • die Felder C des Modells M einer Seite P werden mit den auf der Seite P eingegebenen Werten mithilfe der Methoden [M].setC(Eingabe) initialisiert. In diesem Schritt können Konvertierungs- und Validierungsprozesse stattfinden, die möglicherweise fehlschlagen. In diesem Fall wird das Ereignis, das den Aufruf von POST für die Seite P ausgelöst hat, nicht verarbeitet, und die Seite wird in der vom Kunden eingegebenen Form an diesen zurückgesendet.

Die Vorlage [Form.java] der Seite [index.xhtml] sieht wie folgt aus:


package forms;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;


@ManagedBean
@RequestScoped
public class Form {
  
  /** Erstellt eine neue Instanz des Formulars */
  public Form() {
  }
  
  // Formularfelder
  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";
  
  // Ereignisse
  public String submit(){
    return null;
  }
  
  // Getter und Setter
  ...
}

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

2.5.8. Die Seite [index.xhtml]

Die Seite [index.xhtml], die die vorherige Ansicht 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}">
    <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">
        <!-- Sprachen -->
        <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">
          <!-- Kopfzeilen -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
          <!-- Zeile 1 -->
          ...
          <!-- Zeile 2 -->
          ...
          <!-- Zeile 3 -->
          ...
          <!-- Zeile 4 -->
          ...
          <!-- Zeile 5 -->
          ...
          <!-- Zeile 6 -->
          ...
          <!-- Zeile 7 -->
          ...
          <!-- Zeile 8 -->
          ...
          <!-- Zeile 9 -->
          ...
          <!-- Zeile 10 -->
          ...
          <!-- Zeile 11 -->
          ...
          <!-- Zeile 12 -->
          ...
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

Wir werden nun nacheinander die wichtigsten Komponenten dieser Seite betrachten. Beachten Sie die allgemeine Struktur eines Formulars JSF:


<?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>

Die Komponenten eines Formulars müssen sich innerhalb eines <h:form>-Tags befinden (Zeilen 12–16). Das <f:view>-Tag (Zeilen 7–18) ist erforderlich, wenn die Anwendung internationalisiert wird. Außerdem muss ein Formular über eine Möglichkeit zum Absenden verfügen (POST), häufig in Form eines Links oder einer Schaltfläche wie in Zeile 14. Es kann auch durch zahlreiche 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 Formular-Tabelle besser lesbar zu machen, wird das Formular durch ein Stylesheet ergänzt:


  <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 Tags HTML <head> durch das folgende Tag definiert:

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

Das Stylesheet wird im Ordner [resources] gesucht:

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 eine Anwendung dieses Stylesheets an:


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

Das Tag <h:panelGrid columns="3"/> definiert eine Tabelle mit drei Spalten. Mit dem Attribut columnClasses können diese Spalten gestylt werden. Die Werte col1, col2, col3 des Attributs columnClasses bezeichnen die jeweiligen Stile der Spalten 1, 2 und 3 der Tabelle. Diese Stile werden im Stylesheet der Seite gesucht:


.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 einzelnen Spalten fest.

  • Zeilen 19–23: Der Stil „entete“ dient dazu, das Aussehen des Textes in der ersten Zeile der Tabelle festzulegen:

          <!-- Kopfzeilen -->
          <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“ dient dazu, das Aussehen der Texte in der ersten Spalte der Tabelle festzulegen:

          <!-- Zeile 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 Erstellung zudem oft Spezialisten überlassen wird. Dennoch wollten wir ein minimalistisches Stylesheet verwenden, um daran zu erinnern, dass deren Einsatz unverzichtbar 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 Attribut „style“ des Tags <h:body> festgelegt. Mit diesem Attribut lassen sich Stilelemente festlegen. Das Hintergrundbild befindet sich im Ordner [resources/images/standard.jpg]:

Dieses Bild wird über die Dateien URL und [/mv-jsf2-03/resources/images/standard.jpg] abgerufen. Man könnte also 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 über den Ausdruck EL ${request.contextPath} abgerufen werden. Daher wird das folgende style-Attribut bevorzugt:


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

das unabhängig vom Kontext gültig ist.

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

Kehren wir zu dem zurück, was bereits in Abschnitt 2.5.7 im allgemeinen Fall erläutert wurde, und wenden wir es auf das untersuchte Formular an. Dieses wird in der klassischen Umgebung JSF getestet:

Hier gibt es weder Ereignisbehandler noch eine [métier]-Schicht. Die Schritte [2x] existieren daher nicht. Es wird unterschieden zwischen dem Fall, in dem das Formular F ursprünglich vom Browser angefordert wird, und dem Fall, in dem der Benutzer ein Ereignis im Formular F ausgelöst hat und dieses vom Controller [Faces Servlet] verarbeitet wird. Es gibt zwei unterschiedliche Zyklen von Client-Anfrage und Server-Antwort.

  • Der erste, der der ursprünglichen Anforderung der Seite entspricht, wird durch eine Operation GET des Browsers auf das Formular URL ausgelöst,
  • der zweite, der dem Absenden der auf der Seite eingegebenen Werte entspricht, wird durch eine Operation POST auf dieselbe Operation URL ausgelöst.

Je nach Art der Anfrage GET oder POST des Browsers unterscheidet sich die Verarbeitung der Anfrage durch das Steuerelement [Faces Servlet].

[cas 1 – demande initiale du formulaire F]

Der Browser fordert die Seite URL mit einem GET an. Der Controller [Faces Servlet] springt direkt zum Schritt [4] der Antwortdarstellung. Das Formular [index.xhtml] wird anhand seines Modells [Form.java] initialisiert und an den Client gesendet, der die folgende Ansicht erhält:

Image

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

Anfrage HTTP vom Client:

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

In Zeile 1 ist die Antwort GET des Browsers zu sehen.

Antwort HTTP 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 leere Zeile und der Code HTML des Formulars. Dieser Code wird vom Browser interpretiert und angezeigt.

[cas 2 – traitement des valeurs saisies dans le formulaire F]

Der Benutzer füllt das Formular aus und übermittelt es über die Schaltfläche [Valider]. Der Browser fordert daraufhin den URL des Formulars mit einem POST an. Der Controller [Faces Servlet] verarbeitet diese Anfrage, aktualisiert die Vorlage [Form.java] des Formulars [index.xhtml] und sendet das durch diese neue Vorlage aktualisierte Formular [index.xhtml] erneut zurück. Betrachten wir diesen Zyklus anhand eines Beispiels:

Image

Oben hat der Benutzer seine Eingaben vorgenommen und diese bestätigt. Als Antwort erhält er die folgende Ansicht:

Image

Der Datenaustausch zwischen Client und Server über HTTP sieht in diesem Fall wie folgt aus:

Anfrage HTTP vom Client:

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 erstellte Antwort POST. In Zeile 14 die vom Benutzer eingegebenen Werte. Dort findet man beispielsweise den Text, der in das Eingabefeld eingegeben wurde:

formulaire%3AinputText=nouveau+texte

In Zeile 14 wurde das versteckte Feld javax.faces.ViewState übermittelt. Dieses Feld stellt in verschlüsselter Form den Zustand des Formulars dar, wie er ursprünglich beim ersten GET an den Browser gesendet wurde.

Antwort HTTP 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 Code HTML des Formulars, das anhand seiner neuen Vorlage aus POST aktualisiert wurde.

Wir betrachten nun die verschiedenen Komponenten dieses Formulars.

2.5.11. Tag <h:inputText>

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

Betrachten wir den folgenden Code:


          <!-- Zeile 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 sein Template [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 Codes XHTML generiert [1],
  • Das Tag <h:panelGroup> (Zeilen 3–6) ermöglicht es, mehrere Elemente in einer einzigen Zelle der Tabelle zusammenzufassen, die durch das Tag <h:panelGrid> in Zeile 20 des vollständigen Quellcodes 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 von [Form.java] (Zeilen 3–5 des Java-Codes) verwendet, um den Text des Eingabefelds zu generieren,
  • Zeile 7 des Codes XHTML generiert [4]. Es ist erneut die Methode getInputText von [Form.java], die zur Generierung des Textes [4] verwendet wird.

Der von der Seite XHTML generierte Datenfluss HTML sieht wie folgt aus:


<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 Tags HTML <tr> und <td> werden durch das Tag <h:panelGrid> 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 [Valider] [2] ab. Als Antwort erhalten wir die 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 validiert:


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

Das Tag <h:commandButton> hat kein „action“-Attribut. In diesem Fall wird weder ein Ereignis-Handler aufgerufen noch eine Navigationsregel ausgeführt. Nach der Verarbeitung wird dieselbe Seite zurückgegeben. Sehen wir uns den Verarbeitungsablauf noch einmal an:

Image

  • In [A] wird die Seite P so wiederhergestellt, wie sie gesendet wurde. Das bedeutet, dass die Komponente mit der ID inputText mit ihrem ursprünglichen Wert „text“ wiederhergestellt wird,
  • in [B] werden die vom Browser übermittelten Werte (vom Benutzer eingegeben) den Komponenten der Seite P zugewiesen. Hier erhält die Komponente mit der ID inputText den Wert „ein neuer Text“,
  • in [C] finden die Konvertierungen und Validierungen statt. Hier gibt es keine. Im Modell M lautet das der Komponente mit der ID inputText zugeordnete Feld wie folgt:

private String inputText="texte";

Da die eingegebenen Werte vom Typ String sind, ist keine Konvertierung erforderlich. Außerdem wurde keine Validierungsregel angelegt. Wir werden diese zu einem späteren Zeitpunkt erstellen.

  • In [D] werden die eingegebenen Werte der Vorlage zugewiesen. Das Feld inputText von [Form.java] erhält den Wert „ein neuer Text“,
  • in [E] geschieht nichts, da der Schaltfläche [Valider] kein Ereignis-Handler zugewiesen wurde.
  • In [F] wird die Seite P erneut an den Client gesendet, da die Schaltfläche [Valider] kein „action“-Attribut hat. Die folgenden Zeilen von [index.xhtml] werden dann ausgeführt:

          <!-- Zeile 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}"/>

Die Zeilen 5 und 7 verwenden den Wert des Feldes inputText aus der Vorlage, der nun „ein neuer Text“ lautet. Daraus ergibt sich folgende Anzeige:

Image

2.5.12. Tag <h:inputSecret>

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

Betrachten wir den folgenden Code:


          <!-- Zeile 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 seine Vorlage in [Form.java]:


private String inputSecret="secret";

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

  • Zeile 2 des Codes XHTML generiert [1]
  • Der Text [2] wird durch Zeile 4 generiert. Das Eingabefeld [3] wird durch die Zeile [5] generiert. Normalerweise hätte die Methode getInputSecret von [Form.java] verwendet werden müssen, um den Text des Eingabefelds zu generieren. Eine Ausnahme besteht, wenn es sich um ein „Passwort“ handelt. Das Tag <h:inputSecret> dient nur zum Auslesen einer Eingabe, nicht zum Anzeigen.
  • Zeile 7 des Codes XHTML generiert [4]. Hier wurde die Methode getInputSecret von [Form.java] verwendet, um den Text [4] zu generieren (siehe Zeile 1 des Java-Codes).

Der von der Seite XHTML generierte Datenfluss HTML 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: Das Tag HTML <input type= "password " .../>, das durch das Tag JSF <h:inputSecret> generiert wurde

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

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

formulaire%3AinputSecret=mdp

Die Validierung des Formulars durch [2] führte zur Aktualisierung des Modells [Form.java] durch den Eintrag [1]. Das Feld inputSecret von [Form.java] erhielt daraufhin den Wert „mdp“. Da das Formular [index.xhtml] weder Navigationsregeln noch Ereignisbehandler definiert hat, wird es nach der Aktualisierung seiner Vorlage erneut angezeigt. Man gelangt somit wieder zur Darstellung, die bei der ursprünglichen Aufrufe der Seite [index.xhtml] angezeigt wurde, wobei sich lediglich der Wert des Feldes inputSecret im Modell von [3] geändert hat.

2.5.13. Tag <h:inputTextArea>

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

Betrachten wir den folgenden Code:


          <!-- Zeile 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 seine Vorlage in [Form.java]:


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

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

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

Der von der Seite XHTML generierte Feed HTML 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: Das Tag HTML <textarea>...</textarea>, generiert durch das Tag JSF <h:inputTextArea>

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

Der Wert des gesendeten Felds [1] lautet wie folgt:

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

Die Validierung des Formulars durch [2] führte zur Aktualisierung der Vorlage [Form.java] durch den Eintrag [1]. Das Feld textArea von [Form.java] erhielt daraufhin den Wert „Tutorial JSF\nTeil1“. Die erneute Anzeige von [index.xhtml] zeigt, dass das Feld textArea der Vorlage tatsächlich auf [3] aktualisiert wurde.

2.5.14. Tag <h:selectOneListBox>

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

Betrachten wir den folgenden Code:


<!-- Zeile 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 dessen 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 Codes XHTML generiert [1]
  • Der Text „[2]“ wird durch Zeile 4 generiert. Die Dropdown-Liste „[3]“ wird durch die Zeilen „[5-9]“ generiert. Der Wert des Attributs „size=“ „1“ sorgt dafür, dass die Liste nur ein Element anzeigt. Fehlt dieses Attribut, ist der Standardwert des Attributs „size“ 1. Die Elemente der Liste wurden durch die Tags <f:selectItem> in den Zeilen 6–8 generiert. Diese Tags haben 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. Dieser Wert wird an den Controller [Faces Servlet] gesendet, wenn das Element in der Dropdown-Liste ausgewählt wird.

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

  • Zeile 11 des Codes XHTML generiert [4]. Hier wurde erneut die Methode getSelectOneListBox1 von [Form.java] verwendet.

Der von der Seite XHTML generierte Ablauf HTML 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: das Tag HTML <select ...>...</select>, das durch das Tag JSF <h:selectOneListBox> generiert wurde,
  • Zeilen 4–6: die Tags HTML <option ...> ... </option>, die durch die Tags JSF <f:selectItem> generiert wurden,
  • Zeile 5: Die Tatsache, dass das Element mit dem Wert „2“ in der Liste ausgewählt ist, führt dazu, dass das Attribut „selected=“selected““ vorhanden ist.

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

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

formulaire%3AselectOneListBox1=3

Die Validierung des Formulars durch [2] hat dazu geführt, dass das Modell [Form.java] durch den Eintrag [1] aktualisiert wurde. Das Element HTML


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

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


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

Das Steuerelement JSF wird die Methode setSelectOneListBox1("3") verwenden, um das Modell der Dropdown-Liste zu aktualisieren. Nach dieser Aktualisierung wird das Feld des Modells [Form.java]


        private String selectOneListBox1;

enthält nun den Wert „3“.

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

  • Er bestimmt das Element der Dropdown-Liste, das angezeigt werden soll ([3]),
  • der Wert des Feldes selectOneListBox1 wird in [4] angezeigt.

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


<!-- Zeile 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 Tag <h:selectOneListBox> in Zeile 5 lautet wie folgt:


  private String selectOneListBox2="3";

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

  • Zeile 2 des Codes XHTML erzeugt [1],
  • der Text [2] wird durch Zeile 4 generiert. Die Liste mit Bildlaufleiste [3] wird durch die Zeilen [5-11] generiert. Der Wert des Attributs size= „3 “ sorgt dafür, dass eine Liste mit Schieberegler anstelle einer Dropdown-Liste angezeigt wird. Die Elemente der Liste wurden durch die Tags <f:selectItem> in den Zeilen 6–8 generiert,

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

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

Der von der Seite XHTML generierte Ablauf HTML 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 dem Wert „3“ in der Liste ausgewählt ist, spiegelt sich im Vorhandensein des Attributs „selected=“selected““ wider.

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

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

formulaire%3AselectOneListBox2=5

Die Validierung des Formulars durch [2] hat dazu geführt, dass das Modell [Form.java] durch den Eintrag [1] aktualisiert wurde. Das Element HTML


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

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


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

Das Steuerelement JSF wird die Methode setSelectOneListBox2("5") verwenden, um die Vorlage der Liste zu aktualisieren. Nach dieser Aktualisierung wird das Feld


        private String selectOneListBox2;

nun den Wert „5“.

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

  • Er bestimmt das Listenelement, das ausgewählt werden soll: [3],
  • Der Wert des Feldes selectOneListBox2 wird als [4] angezeigt.

2.5.15. Tag <h:selectManyListBox>

Das Tag <h:selectmanyListBox> generiert ein Tag HTML <select multiple="multiple">...</select>, mit dem der Benutzer mehrere Elemente aus einer Liste auswählen kann.

Betrachten wir den folgenden Code:


<!-- Zeile 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 dessen Vorlage in [Form.java]:


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

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

  • Zeile 2 des Codes XHTML 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 einem bestimmten Zeitpunkt drei dieser Elemente anzeigt. Die in der Liste ausgewählten Elemente wurden durch den Aufruf der Methode getSelectManyListBox() (Zeile 5) des Java-Modells ermittelt. Das erhaltene Ergebnis {"1","3"} (Zeile 1 des Java-Codes) ist ein Array von Elementen vom Typ String. Jedes dieser Elemente dient dazu, eines der Elemente der Liste auszuwählen. Hier werden die Elemente in den Zeilen 6 und 10 ausgewählt, deren Attribut itemValue im Array {"1","3"} enthalten ist. Dies zeigt [3].
  • Zeile 14 des Codes XHTML erzeugt [4]. Hier wird nicht die Methode getSelectManyListBox des Java-Modells der Liste aufgerufen, sondern die folgende Methode getSelectManyListBoxValue:

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

Hätte man die Methode getSelectManyListBox aufgerufen, hätte man ein Array von String erhalten. Um dieses Element in den Datenstrom HTML aufzunehmen, hätte der Controller seine Methode toString aufrufen müssen. Diese Methode gibt jedoch für ein Array lediglich dessen „Hashcode“ zurück und nicht die Liste seiner Elemente, wie wir es wünschen. Daher verwenden wir die oben genannte Methode getSelectManyListBoxValue, um eine Zeichenkette zu erhalten, die den Inhalt des Arrays darstellt,

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

Der von der Seite XHTML generierte Datenfluss HTML 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: Das Tag HTML <select multiple= "multiple "...>...</select>, das durch das Tag JSF <h:selectManyListBox> generiert wurde. Das Vorhandensein des Attributs „multiple“ weist darauf hin, dass es sich um eine Liste mit Mehrfachauswahl handelt,
  • die Tatsache, dass das Modell der Liste das String-Array {"1","3"} ist, führt dazu, dass die Listenelemente in Zeile 4 (value="1") und Zeile 6 (value="3") das Attribut selected="selected" haben,
  • Zeile 10: Wenn man auf die Schaltfläche [Raz] klickt, wird der JavaScript-Code des onclick-Attributs ausgeführt. Die Seite wird im Browser durch einen Objektbaum dargestellt, der oft als DOM (Document Object Model) bezeichnet wird. Jedes Objekt des Baums ist für den JavaScript-Code über sein Attribut „name“ zugänglich. Die Liste in Zeile 3 des obigen Codes HTML heißt „formulaire:selectManyListBox“. Das Formular selbst kann auf verschiedene Weise bezeichnet werden. Hier wird es mit der Notation this.form bezeichnet, wobei „this“ die Schaltfläche [Raz] und this.form das Formular bezeichnet, in dem sich diese Schaltfläche befindet. Die Liste „Formular:selectManyListBox“ befindet sich in demselben Formular. Daher bezeichnet die Notation „this.form['formulaire:selectManyListBox']“ die Position der Liste im Komponentenbaum des Formulars. Das Objekt, das eine Liste darstellt, verfügt über ein Attribut selectedIndex, dessen Wert die Nummer des in der Liste ausgewählten Elements ist. Diese Nummer beginnt bei 0 und bezeichnet das erste Element der Liste. Der Wert -1 bedeutet, dass kein Element in der Liste ausgewählt ist. Der JavaScript-Code, der dem Attribut selectedIndex den Wert -1 zuweist, bewirkt, dass alle Elemente der Liste (sofern vorhanden) die Auswahl verlieren.

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

Der Wert des gesendeten Felds [1] lautet wie folgt:

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

Die Validierung des Formulars durch [2] hat zur Aktualisierung der Vorlage [Form.java] durch den Eintrag [1] geführt. Die Elemente HTML


    <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“ und „5“ als Werte der Komponente JSF gesendet, die die Dropdown-Liste erzeugt 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 ist zu sehen, dass der Parameter der Methode ein Array von String ist. In diesem Fall handelt es sich um das Array {"3","4","5"}. Nach dieser Aktualisierung lautet das Feld


        private String[] selectManyListBox;

nun das Array {"3","4","5"}.

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

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

2.5.16. Tag <h:selectOneMenu>

Das Tag <h:selectOneMenu> ist identisch mit dem Tag <h:selectOneListBox size="1">. Im Beispiel lautet der ausgeführte Code JSF wie folgt:


<!-- Zeile 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 Tag <h:selectOneMenu> in [Form.java] lautet wie folgt:


  private String selectOneMenu="1";

Bei der ersten Aufrufe der Seite [index.xhtml] erzeugt der vorstehende Code die folgende Ansicht:

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

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

formulaire%3AselectOneMenu=4

2.5.17. Tag <h:selectManyMenu>

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


<!-- Zeile 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 Tag <h:selectManyMenu> in [Form.java] lautet wie folgt:


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

Bei der ersten Anfrage der Seite [index.xhtml] generiert der vorstehende Code die Seite:

Die Liste [1] enthält die Texte „eins“, …, „fünf“, wobei die Elemente „eins“ und „zwei“ ausgewählt sind. Der generierte Code HTML 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 zu sehen ist, sind die Elemente „un“ und „deux“ ausgewählt (Vorhandensein des Attributs selected).

Es ist schwierig, einen Screenshot eines Ausführungsbeispiels zu zeigen, da die ausgewählten Elemente im Menü nicht dargestellt werden können. Der Leser wird gebeten, den Test selbst durchzuführen (um mehrere Elemente in der Liste auszuwählen, halten Sie die Strg-Taste gedrückt und klicken Sie).

2.5.18. Tag <h:inputHidden>

Das Tag <h:inputHidden> hat keine visuelle Darstellung. Es dient lediglich dazu, ein Tag HTML <input type="hidden" value="..."/> in den HTML-Fluss der Seite einzufügen. Sind sie in einem <h:form>-Tag enthalten, gehören ihre Werte zu den Werten, die beim Absenden des Formulars an den Server übermittelt werden. Da es sich um Formularfelder handelt, die für den Benutzer nicht sichtbar sind, werden sie als versteckte Felder bezeichnet. Der Vorteil dieser Felder besteht darin, dass sie zwischen den verschiedenen Anfrage-/Antwortzyklen desselben Clients Speicherplatz reservieren:

  • Der Client fordert ein Formular F an. Der Server sendet es ihm zu und speichert eine Information I in einem versteckten Feld C in der Form <h:inputHidden id="C" value="I"/>.
  • wenn der Client das Formular F ausgefüllt und an den Server gesendet hat, wird der Wert I des Feldes C an den Server zurückgesendet. Dieser kann dann die Information I wiederfinden, die er auf der Seite gespeichert hatte. Auf diese Weise wurde ein Speicher zwischen den beiden Anfrage-/Antwortzyklen geschaffen,
  • JSF nutzt diese Technik selbst. Die Information I, die es im Formular F speichert, ist der Wert aller Komponenten dieses Formulars. 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 den Wert aller Komponenten der an den Client gesendeten Seite darstellt. Wenn der Client die Seite nach Eingaben im Formular zurücksendet, wird das versteckte Feld javax.faces.ViewState mit den eingegebenen Werten zurückgesendet. Dadurch kann das Steuerelement JSF die Seite so wiederherstellen, wie sie ursprünglich gesendet wurde. Dieser Mechanismus wurde auf Seite 72 erläutert.

Der Code JSF aus dem Beispiel lautet wie folgt:


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

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


  private String inputHidden="initial";

Dies führt bei der ersten Aufrufe der Seite [index.xhtml] zu folgender Anzeige:

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

Der generierte Code HTML 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>

Zum Zeitpunkt der Formularübermittlung (POST) wird der „ursprüngliche“ Wert des Feldes „formular:inputHidden“ in Zeile 3 zusammen mit den anderen Werten des Formulars übermittelt. Das Feld


  private String inputHidden;

wird mit diesem Wert aktualisiert, der bereits ursprünglich darin enthalten war. Dieser Wert wird in die neue Seite integriert, die an den Kunden zurückgesendet wird. Man erhält also immer den oben gezeigten Screenshot.

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

formulaire%3AinputHidden=initial

2.5.19. Tag <h:selectBooleanCheckBox>

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

Betrachten wir den folgenden Code JSF:


<!-- Zeile 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 Tag <h:selectBooleanCheckbox> 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 angezeigte Seite wie folgt aus:

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

Der durch den vorherigen Code JSF generierte Datenfluss HTML 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] ist das generierte HTML-Tag <input type="checkbox"> zu sehen. Der Wert true des zugehörigen Modells hat dazu geführt, dass dem Tag das Attribut checked="checked" hinzugefügt wurde. Dadurch ist das Kontrollkästchen angekreuzt.

Entfernen wir nun unten das Häkchen aus dem Kontrollkästchen [1], senden wir das Formular [2] ab und sehen wir uns das Ergebnis [3, 4] an:

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

Die Validierung des Formulars durch [2] führte zur Aktualisierung der Vorlage [Form.java] durch den Eintrag [1]. Das Feld selectBooleanCheckbox von [Form.java] erhielt daraufhin den Wert „false“. Die erneute Anzeige von [index.xhtml] zeigt, dass das Feld selectBooleanCheckbox des Modells tatsächlich auf [3] und [4] aktualisiert wurde. Interessant ist hier, dass JSF dank des versteckten Feldes javax.faces.ViewState feststellen konnte, dass das ursprünglich aktivierte Kontrollkästchen vom Benutzer deaktiviert worden war. Der Wert eines deaktivierten Kontrollkästchens gehört nämlich nicht zu den vom Browser übermittelten Werten. Dank des im versteckten Feld javax.faces.ViewState gespeicherten Komponentenbaums kann JSF feststellen, dass es im Formular ein Kontrollkästchen mit dem Namen „selectBooleanCheckbox“ gab und dass dessen Wert nicht zu den vom Client-Browser übermittelten Werten gehört. Daraus kann es schließen, dass das Kontrollkästchen im übermittelten Formular deaktiviert war, wodurch es den booleschen Wert false dem zugehörigen Java-Modell zuweisen kann:


  private boolean selectBooleanCheckbox;

2.5.20. Tag <h:selectManyCheckBox>

Das Tag <h:selectManyCheckBox> generiert eine Gruppe von Kontrollkästchen und somit mehrere Tags HTML <input type="checkbox" ...>. Dieses Tag entspricht dem Tag <h:selectManyListBox>, mit dem Unterschied, dass die auszuwählenden Elemente als nebeneinanderliegende Kontrollkästchen statt als Liste dargestellt werden. Was für das Tag <h:selectManyListBox> gesagt wurde, gilt auch hier.

Betrachten wir den folgenden Code JSF:


          <!-- Zeile 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 Tag <h:selectManyCheckbox> 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 angezeigte Seite wie folgt aus:

  • Zeile 2 des Codes XHTML 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"};

Diese Tabelle definiert:

  • Wenn die Seite angezeigt wird, sollen die entsprechenden Kästchen angekreuzt werden. Dies geschieht anhand ihres Werts c.a.d. Ihr Feld lautet itemValue. Im obigen Beispiel werden die Kontrollkästchen mit den Werten aus der Tabelle {"1","3"} markiert. Dies ist auf dem Screenshot oben zu sehen;
  • Wenn die Seite abgeschickt wird, erhält die Vorlage „selectManyCheckbox“ das Array mit den Werten der Kontrollkästchen, die der Benutzer angekreuzt hat. Das werden wir als Nächstes sehen:
  • Zeile 12 des Codes XHTML generiert [4]. Es war 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+"]";
}

Der durch den vorherigen Code JSF generierte Ablauf HTML 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 vom Typ <input type="checkbox" ...> generiert. Die Tags in den Zeilen 3 und 7 besitzen das Attribut checked="checked", wodurch sie als angekreuzt angezeigt werden. Es ist zu beachten, dass sie alle dasselbe Attribut name="formulaire:selectManyCheckbox" haben, d. h., die vier Felder HTML 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 in folgender Form:

formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

und die Vorlage der vier Kontrollkästchen


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

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

Sehen wir uns das im Folgenden an. In [1] nehmen wir die Änderung vor, in [2] senden wir das Formular ab. In [3] erhalten wir folgendes Ergebnis:

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

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

2.5.21. Tag <h:selectOneRadio>

Das Tag <h:selectOneRadio> erzeugt eine Gruppe von Radiobuttons, die sich gegenseitig ausschließen.

Betrachten wir den folgenden Code JSF:


<!-- Zeile 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 Tag <h:selectOneRadio> 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 angezeigte Ansicht wie folgt aus:

  • Zeile 2 des Codes XHTML generiert [1],
  • der Text [2] wird durch Zeile 4 generiert. Die Optionsfelder [3] werden durch die Zeilen 5–10 generiert. 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 Optionsfeld aktiviert ist;

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


  private String selectOneRadio="2";

Dieses Modell legt fest:

  • welches der Radiobuttons beim Anzeigen der Seite markiert sein soll. Dies geschieht über ihren Wert, c.a.d, und ihr Feld itemValue. Im obigen Beispiel wird das Optionsfeld mit dem Wert „2“ ausgewählt. Dies ist auf dem Screenshot oben zu sehen;
  • Wenn die Seite gesendet wird, erhält die Vorlage „selectOneRadio“ den Wert des Radiobuttons, der ausgewählt wurde. Das werden wir als Nächstes sehen,
  • Zeile 12 des Codes XHTML generiert [4].

Der durch den vorherigen Code JSF generierte Datenfluss HTML sieht wie folgt aus:


<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 verfügt über das Attribut checked="checked", wodurch das entsprechende Optionsfeld als ausgewählt angezeigt wird. Es ist zu beachten, dass alle Tags dasselbe Attribut name="Formular:selectOneRadio" haben, d. h., die vier Felder HTML tragen denselben Namen. Dies ist die Voraussetzung für eine Gruppe exklusiver Optionsfelder: Wenn eines markiert ist, sind die anderen nicht markiert.

Unten, in [1], wird eines der Optionsfelder ausgewählt, in [2] wird das Formular abgeschickt, in [3] das erzielte Ergebnis:

Der übermittelte Wert für das Feld [1] 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 der Felder [1] und [2] generiert werden. Sie werden hier dynamisch durch Java-Code generiert, während sie in der vorherigen Version fest im Code der Seite JSF hinterlegt waren.

2.6.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung lautet wie folgt:

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

  • In [1], auf der Seite JSF, werden die Listenelemente nicht mehr „fest“ im Code geschrieben,
  • In [2] wird die Vorlage der Seite JSF [1] geändert,
  • in [3], wobei eine der Nachrichten geändert wird.

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

Die Seite JSF [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">
        <!-- Sprachen -->
        <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">
...
          <!-- Zeile 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}"/>
          <!-- Zeile 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}"/>
          <!-- Zeile 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}"/>
          <!-- Zeile 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}"/>
          <!-- Zeile 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"/>
...
          <!-- Zeile 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}"/>
          <!-- Zeile 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 dargestellt. Wo zuvor folgender Code stand:


<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>

steht nun folgender:


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

Die drei Tags <f:selectItem> in den Zeilen 2–4 wurden durch das einzelne Tag <f:selectItems> in Zeile b ersetzt. Dieser Tag verfügt über ein „value“-Attribut, dessen Wert eine Sammlung von Elementen vom Typ javax.faces.model.SelectItem ist. Im obigen Beispiel 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 vom Typ javax.faces.model.SelectItem zurück, das von der privaten Methode getItems in Zeile 5 erstellt wurde. Es ist zu beachten, dass die Methode getSelectOneListbox1Items nicht die Methode getter eines privaten Feldes selectOneListBox1Items ist;
  • die Klasse javax.faces.model.SelectItem verfügt über verschiedene Konstruktoren.

Image

Wir verwenden Zeile 8 der Methode getItems, den Konstruktor SelectItem(Object value, String label), der dem Tag JSF entspricht


    <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 Element i durch den Konstruktor SelectItem(i, label+i) erzeugt wird.

Der Code JSF


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

ist dann funktional gleichbedeutend mit dem folgenden Code JSF:


<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 Seite JSF. So finden sich in der Vorlage [Form.java] 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 Meldung 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

Der Leser ist eingeladen, diese neue Version zu testen.

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

Betrachten wir die ursprüngliche Anfrage der Seite JSF [index.xhtml] durch einen GET des Browsers:

  • Die Seite JSF wird von [1] angefordert,
  • der Controller [Faces Servlet] fordert deren Anzeige in [3] an. Die Engine JSF, die die Seite verarbeitet, greift auf das Modell [Form.java] dieser Seite zurück, beispielsweise auf die Methode getSelectOneListBox1Items. Diese Methode könnte sehr wohl ein Array von Elementen vom Typ SelectItem zurückgeben, basierend auf Informationen, die in einer Datenbank gespeichert sind. Dazu würde sie die Schicht [métier] [2b] aufrufen.

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

2.7.1. Die Anwendung

Die Anwendung ist dieselbe wie zuvor, nur dass das Formular nun als mehrseitiger Assistent dargestellt wird:

  • in [1], Seite 1 des Formulars – kann auch über den Link 1 von [2] aufgerufen werden
  • in [2], eine Gruppe von 5 Links.
  • in [3], Seite 2 des Formulars, die über den Link 2 von [2] aufgerufen wird
  • in [4], Seite 3 des Formulars, die über den Link 3 von [2] aufgerufen wird
  • in [5], die Seite, die über den Link „Ausnahme auslösen“ von [2] aufgerufen wird
  • in [6], die Seite, die über den Link 4 von [2] aufgerufen wird. Sie fasst die Eingaben auf den Seiten 1 bis 3 zusammen.

2.7.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung lautet wie folgt:

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

  1. In [1] wurde die Seite JSF [index.xhtml] in drei Seiten [form1.xhtml, form2.xhtml, form3.xhtml] aufgeteilt, auf die die Eingaben 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 vorhergehenden Seiten JSF,
  2. in [3] wird eine Seite [exception.xhtml] hinzugefügt: Sie wird verwendet, wenn in der Anwendung eine Ausnahme auftritt.

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

2.7.3.1. Der Code der Seiten XHTML

Die Seite JSF [form1.xhtml] 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">
        <!-- 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"/>
          <!-- Zeile 1 -->
          <h:outputText value="inputText"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.loginPrompt']}"/>
            <h:inputText id="inputText" value="#{form.inputText}"/>
          </h:panelGroup>
          <!-- Zeile 2 -->
          <h:outputText value="inputSecret"  styleClass="info"/>
          <h:panelGroup>
            <h:outputText value="#{msg['form.passwdPrompt']}"/>
            <h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
          </h:panelGroup>
          <!-- Zeile 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:

Folgende Punkte sind zu beachten:

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

// Ereignisse
  public String doAction2(){
    return "form2";
  }
  
  public String doAction4(){
    return "form4";
  }
  
  public String doAlea(){
    // eine Zufallszahl zwischen 1 und 3
    int i=1+(int)(3*Math.random());
    // den Navigationsschlüssel zurückgeben
    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 werden später darauf zurückkommen. Die Methoden doAction2 und doAction4 geben lediglich den Navigationsschlüssel zurück, ohne weitere Verarbeitung durchzuführen. Man hätte also 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 hingegen einen zufälligen Navigationsschlüssel, dessen Wert aus der Menge {"form1", "form2", "form3"} stammt.

Der Code der Seiten [form2.xhtml, form3.xhtml, form3.xhtml] entspricht dem der Seite [form1.xhtml].

2.7.3.2. Lebensdauer des Modells [Form.java] der Seiten [form*.xhtml]

Betrachten wir die folgende Abfolge von Aktionen:

  • in [1] wird Seite 1 ausgefüllt und man wechselt zu Seite 3,
  • in [2] wird Seite 3 ausgefüllt und man kehrt zu Seite 1 zurück,
  • in [3] findet man Seite 1 so wieder, wie sie eingegeben wurde. Man kehrt dann zu Seite 3 zurück,
  • in [4] wird Seite 3 so wiedergefunden, 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:

  • Die Vorlage [Form.java] wird mit POST aus [form1.jsp] aktualisiert. Insbesondere erhält das Feld inputText den Wert „ein anderer Text“,
  • der Navigationsschlüssel „form3“ bewirkt die Anzeige von [form3.xhtml]. Das in [form3.xhtml] eingebettete ViewState enthält nur den Status der Komponenten von [form3.xhtml], nicht den von [form1.xhtml].

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

  • wird das Modell [Form.java] mit dem Modell POST aus [form3.xhtml] aktualisiert. Wenn die Lebensdauer des Modells [Form.java] abgelaufen ist, wird ein brandneues Objekt [Form.java] erstellt, bevor es durch das Modell 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: Denn im POST von [form3.xhtml] gibt es nichts, was das Feld inputText aktualisiert, das Teil der Vorlage von [form1.xhtml] ist und nicht Teil der Vorlage von [form3.xhtml],

  • Der Navigationsschlüssel „form1“ bewirkt die Anzeige von [form1.xhtml]. Die Seite zeigt ihre Vorlage an. In unserem Fall zeigt das Eingabefeld login, das mit der Vorlage inputText verknüpft ist, den Wert texte an und nicht den in [1] eingegebenen Wert „ein anderer Text“. Damit das Feld inputText den in [1] eingegebenen Wert beibehält, muss die Lebensdauer der Vorlage [Form.java] auf „Sitzung“ und nicht auf „Anfrage“ gesetzt sein. In diesem Fall
    • wird die Vorlage nach Abschluss von POST aus [form1.xhtml] in die Sitzung des Kunden übernommen. Das Feld inputText enthält den Wert „ein anderer Text“,
    • zum Zeitpunkt des POST aus [form3.xhtml] wird die Vorlage in dieser Sitzung gesucht und durch den POST aus [form3.xhtml] aktualisiert. Das Feld inputText wird durch diesen POST nicht aktualisiert, sondern behält den Wert „ein anderer Text“ bei, beibehalten, der am Ende des POST aus [form1.xhtml] und [1] übernommen wurde.

Die Deklaration des Beans [Form.java] 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 der Bean einen Sitzungsbereich zu.

2.7.4. Ausnahmebehandlung

Kehren wir zur allgemeinen Architektur einer JSF-Anwendung zurück:

Was passiert, wenn ein Ereignisbehandler oder ein Modell eine Ausnahme aus der Geschäftslogikschicht abfängt, beispielsweise eine unerwartete Trennung von einer Datenbank?

  • Die Ereignisbehandler [2a] können jede Ausnahme abfangen, die aus der Schicht [métier] weitergeleitet wird, und dem Controller [Faces Servlet] einen Navigationsschlüssel zu einer für die Ausnahme spezifischen Fehlerseite übergeben.
  • Für die Vorlagen ist diese Lösung jedoch nicht anwendbar, da sich das System bei deren Aufruf ([3,4]) bereits in der Rendering-Phase einer bestimmten Seite (XHTML) befindet und nicht mehr in der Auswahlphase dieser Seite. Wie kann man die Seite wechseln, während man sich gerade in der Darstellungsphase einer dieser Seiten befindet? Eine einfache Lösung, die jedoch nicht immer geeignet ist, besteht darin, die Ausnahme nicht zu behandeln, sodass sie bis zum Servlet-Container weitergeleitet wird, der die Anwendung ausführt. Dieser kann so konfiguriert werden, dass er eine bestimmte Seite anzeigt, wenn eine Ausnahme bis zum Servlet-Container weitergeleitet wird. Diese Lösung ist immer anwendbar und wird nun näher betrachtet.

2.7.4.1. Konfiguration 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>

In den Zeilen 32–39 finden sich die Definitionen von zwei Fehlerseiten. Es können so viele <error-page>-Tags wie nötig verwendet werden. Das <location>-Tag gibt die Seite an, die im Fehlerfall angezeigt werden soll. Der mit der Seite verknüpfte Fehlertyp kann auf zwei Arten definiert werden:

  • über das <exception-type>-Tag, das den Java-Typ der behandelten Ausnahme definiert. So gibt das <error-page>-Tag in den Zeilen 36–39 an, dass der Servlet-Container, wenn er während der Ausführung der Anwendung eine Ausnahme vom Typ [java.lang.Exception] oder einem davon abgeleiteten Typ (Zeile 37) abfängt, die Seite [/faces/exception.xhtml] (Zeile 38) anzeigen. Indem hier der allgemeinste Ausnahmetyp [java.lang.Exception] verwendet wird, wird sichergestellt, dass alle Ausnahmen behandelt werden,
  • durch das Tag <error-code> (Zeile 33), das einen Fehlercode HTTP definiert. Wenn beispielsweise ein Browser die Seite URL [http://machine:port/contexte/P] anfordert und die Seite P im Anwendungskontext nicht existiert, greift die Anwendung nicht in die Antwort ein. Der Servlet-Container generiert diese Antwort, indem er eine Standard-Fehlerseite sendet. Die erste Zeile des Datenstroms HTTP in dieser Antwort enthält den Fehlercode 404, der angibt, dass die angeforderte Seite P nicht existiert. Möglicherweise möchte man eine Antwort generieren, die beispielsweise dem grafischen Erscheinungsbild der Anwendung entspricht oder Links zur Behebung des Problems enthält. In diesem Fall verwendet man ein <error-page>-Tag mit einem <error-code>404</error-code>-Tag.

Oben ist der Fehlercode HTTP für den Fehler 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 durchlaufen würde. Die beiden <error-page>-Tags in den Zeilen 28–35 sind daher wahrscheinlich redundant. Wir haben beide eingefügt, um die beiden Möglichkeiten der Fehlerbehandlung zu veranschaulichen.

2.7.4.2. Simulation der Ausnahme

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

Ein Klick auf den Link [Lancer une exception] [1] führt zur Anzeige der Seite [2].

Im Quellcode der Seiten [formx.xhtml] wird der Link [Lancer une exception] wie folgt generiert:


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

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


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

Darin wird eine Ausnahme vom Typ [java.lang.Exception] ausgelöst. Diese wird an den 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, lässt dieser die entsprechende Fehlerseite anzeigen und übermittelt ihr dabei Informationen zur Ausnahme. Diese werden als neue Attribute der gerade bearbeiteten Anfrage hinzugefügt. Die Anfrage eines Browsers und die Antwort, die er erhält, werden in Java-Objekte vom Typ [HttpServletRequest request] und [HttpServletResponse response] gekapselt. Diese Objekte stehen in allen Phasen der Verarbeitung der Browseranfrage zur Verfügung.

Nach Erhalt der Anfrage HTTP vom Browser kapselt der Servlet-Container diese in das Java-Objekt [HttpServletRequest request] ein und erstellt das Objekt [HttpServletResponse response], mit dem die Antwort generiert wird. In diesem Objekt befindet sich insbesondere der Kanal TCP-IP, der für den Datenstrom HTTP der Antwort verwendet wird. Alle Schichten t1, t2, …, tn, die an der Verarbeitung des Objekts request beteiligt sind, haben Zugriff auf diese beiden Objekte. Jede von ihnen kann auf die Elemente der ursprünglichen Anfrage request zugreifen und die Antwort vorbereiten, indem sie das Objekt response ergänzt. Eine Schicht von localisation kann beispielsweise die localisation der Antwort über die Methode response.setLocale(Locale l) festlegen.

Die verschiedenen ti-Schichten können sich über das Objekt request Informationen weitergeben. Dieses verfügt über ein Attributwörterbuch, das bei seiner Erstellung leer ist und von den nachfolgenden Verarbeitungsschichten ergänzt werden kann. Diese können in die Attribute des Objekts request Informationen einfügen, die für die nächste Verarbeitungsschicht erforderlich sind. Es gibt zwei Methoden zur Verwaltung der Attribute des Objekts request:

  • void setAttribute(String s, Object o), mit der den Attributen ein durch die Zeichenfolge s identifiziertes Objekt o hinzugefügt werden kann,
  • Object getAttribute(String s), mit der das durch die Zeichenfolge s identifizierte Attribut o abgerufen werden kann.

Wenn eine Ausnahme bis zum Servlet-Container weitergeleitet wird, fügt dieser die folgenden Attribute in die aktuell verarbeitete Anfrage ein:

Schlüssel
Wert
javax.servlet.error.status_code
der Fehlercode HTTP, 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 Attribute der Anfrage 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. Die Ausdrücke auf der Ausnahmeseite

In der Verarbeitungskette der Client-Anfrage ist die Seite XHTML normalerweise das letzte Glied der Kette:

Alle Elemente der Kette sind Java-Klassen, einschließlich der Seite XHTML. Diese wird nämlich vom Servlet-Container in ein Servlet umgewandelt, c.a.d, in eine normale Java-Klasse. Genauer gesagt wird die Seite XHTML 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 findet sich der Java-Code, der der Seite XHTML entspricht. Dieser Code verfügt über eine Reihe von Objekten, die durch die Methode _jspService (Zeile 1 oben) initialisiert wurden:

  • Zeile 1: HttpServletRequest request: die gerade 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 Objekt request kann auch das Objekt application Attribute haben. Diese werden von allen Anfragen aller Kunden gemeinsam genutzt. In der Regel handelt es sich dabei um schreibgeschützte Attribute,
  • Zeile 6: HTTPSession Sitzung: stellt die Sitzung des Clients dar. Wie die Objekte request und application kann auch das Objekt session Attribute haben. Diese werden von allen Anfragen desselben Kunden gemeinsam genutzt,
  • Zeile 9: JspWriter out: ein Schreibstrom zum Client-Browser. Dieses Objekt ist nützlich für die Fehlersuche auf einer XHTML-Seite. Alles, was über out.println(text) geschrieben wird, wird im Client-Browser angezeigt.

Wenn auf der Seite JSF #{Ausdruck} geschrieben wird, kann „Ausdruck“ der Schlüssel eines Attributs der oben genannten Objekte „request“, „session“ oder „application“ sein. Das entsprechende Attribut wird nacheinander in diesen drei Objekten gesucht. Somit wird #{Schlüssel} wie folgt ausgewertet:

  1. request.getAttribute(Schlüssel)
  2. session.getAttribute(Schlüssel)
  3. application.getAttribute(Schlüssel)

Sobald ein Wert gefunden wird, der nicht null lautet, wird die Auswertung von #{Schlüssel} beendet. Um genauer zu sein, kann man den Kontext angeben, in dem das Attribut gesucht werden soll:

  • #{requestScope['clé']}, um das Attribut im Request-Objekt zu suchen,
  • #{sessionScope['clé']}, um das Attribut im Objekt „session“ zu suchen,
  • #{applicationScope['clé']}, um das Attribut im Anwendungsobjekt zu suchen.

Dies wurde auf der Seite [exception.xhtml] auf Seite 116 umgesetzt. Folgende Attribute wurden verwendet:

Schlüssel
Bereich
Wert
javax.servlet.error.status_code
Anfrage
siehe Abschnitt 2.7.4.3.
javax.servlet.error.exception
ebenso
ebenso
javax.servlet.error.request_uri
ebenso
dasselbe
javax.servlet.error.servlet_name
idem
wie oben

Die verschiedenen Meldungen, die für die Seite JSF [exception.xhtml] erforderlich sind, wurden den bereits vorhandenen 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 der Eingaben

2.8.1. Die Anwendung

Die Anwendung zeigt ein Eingabeformular an. Nach dessen Validierung wird dasselbe Formular als Antwort zurückgesendet, gegebenenfalls zusammen mit Fehlermeldungen, falls die Eingaben als fehlerhaft erkannt wurden.

2.8.2. Das NetBeans-Projekt

Das NetBeans-Projekt der 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 ausschließlich auf Französisch ([3]). Eine Option zum Wechseln der Sprache ist nicht verfügbar.

2.8.3. Die Anwendungsumgebung

Wir geben hier den Inhalt der Konfigurationsdateien der Anwendung wieder, ohne besondere Erläuterungen hinzuzufügen. Diese Dateien ermöglichen ein besseres Verständnis der folgenden Ausführungen.

[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">
        <!-- Zeile 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"/>
        <!-- Zeile 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}"/>
        <!-- Zeile 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 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}"/>
        <!-- Zeile 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 6 -->
        ...
        <!-- Zeile 7 -->
        ...
        <!-- Zeile 8 -->
        ...
        <!-- Zeile 9 -->
        ...
        <!-- Zeile 10 -->
        ...
        <!-- Zeile 11 -->
        ...
        <!-- Zeile 12 -->
        ...
        <!-- Zeile 13 -->
        ...
      </h:panelGrid>
      <!-- Bedienelemente -->
      <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 Neuerung ist das Vorhandensein von Tags:

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

Die Vorlage dieser 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() {
}
// Eingaben
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 = "";

// Aktionen
public String submit() {
...
}

public String cancel() {
...
}

// Validatoren
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// Getter und Setter
...
}

Neu ist hier, dass die Felder der Vorlage nicht mehr ausschließlich vom Typ String sind, sondern verschiedene Typen aufweisen.

2.8.5. Die verschiedenen Eingaben im Formular

Wir betrachten nun nacheinander die verschiedenen Eingaben des Formulars.

2.8.5.1. Eingaben 1 bis 4: Eingabe einer ganzen Zahl

Die Seite [index.xhtml] zeigt Eingabe 1 in folgender Form:


<!-- Zeile 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 Vorlage form.saisie1 ist in [Form.java] wie folgt definiert:


private Integer saisie1 = 0;

In einem GET des Browsers wird die Seite [index.xhtml], die mit ihrer Vorlage [Form.java] verknüpft ist, optisch wie folgt dargestellt:

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

Angenommen, die folgende Eingabe wird vorgenommen und anschließend bestätigt:

Man erhält dann das folgende Ergebnis in dem von der Anwendung zurückgegebenen Formular:

  • in [1], die fehlerhafte Eingabe,
  • in [2] die entsprechende Fehlermeldung,
  • in [3] sieht man, dass sich der Wert des Feldes „Integer saisie1“ des Modells nicht geändert hat.

Erläutern wir, was passiert ist. Kehren wir dazu zum Verarbeitungszyklus einer Seite JSF zurück:

Wir betrachten diesen Ablauf für die Komponente:


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

und deren Modell:


private Integer saisie1 = 0;
  • In [A] wird die Seite [index.xhtml] wiederhergestellt, die beim GET des Browsers gesendet wurde. In [A] entspricht die Seite dem Zustand, in dem der Benutzer sie erhalten hat. Die Komponente id="saisie1" erhält wieder ihren Ausgangswert „0“,
  • in [B] erhalten die Komponenten der Seite die vom Browser übermittelten Werte. In [B] entspricht die Seite dem Zustand, in dem der Benutzer sie eingegeben und bestätigt hat. Die Komponente id="saisie1" erhält als Wert den übermittelten Wert „x“,
  • in [C] werden, sofern die Seite Validatoren und explizite Konverter enthält, diese ausgeführt. Implizite Konverter werden ebenfalls ausgeführt, wenn der Typ des der Komponente zugeordneten Feldes nicht vom Typ String ist. Dies ist hier der Fall, da das Feld form.saisie1 vom Typ Integer ist. JSF wird versuchen, den Wert „x“ der Komponente id="saisie1" in den Typ Integer umzuwandeln. Dies führt zu einem Fehler, der den Verarbeitungszyklus [A-F] abbricht. Dieser Fehler wird der Komponente id="saisie1" zugeordnet. Über [D2] wird anschließend direkt zur Rendering-Phase der Antwort übergegangen. Es wird dieselbe Seite [index.xhtml] zurückgegeben,
  • die Phase [D] findet nur statt, wenn alle Komponenten einer Seite die Konvertierungs-/Validierungsphase erfolgreich durchlaufen haben. In dieser Phase wird der Wert der Komponente id="saisie1" ihrem Modell form.saisie1 zugewiesen.

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


<!-- Zeile 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 in [2] angezeigte Meldung stammt aus Zeile 4 von [index.xhtml]. Das Tag <h:message for="idComposant"/> zeigt die Fehlermeldung an, die der durch das Attribut „for“ bezeichneten Komponente zugeordnet ist, sofern ein Fehler vorliegt. Die in [2] angezeigte Meldung ist standardisiert und befindet sich in der Datei [javax/faces/Messages.properties] des Archivs [jsf-api.jar]:

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

...
# ==============================================================================
# Komponentenfehler
# ==============================================================================
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.

# ==============================================================================
# Konverterfehler
# ==============================================================================
...
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}
...


# ==============================================================================
# Validatorfehler
# ==============================================================================
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 bei 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 in den Typ Integer. Die zugehörige Fehlermeldung befindet sich 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 zu erkennen, dass:

  • der Parameter {2} durch die ID der Komponente ersetzt wurde, bei der der Konvertierungsfehler aufgetreten ist,
  • der Parameter {0} durch die in [1] für die Komponente vorgenommene Eingabe ersetzt wurde,
  • der Parameter {1} durch die Zahl 9346 ersetzt wurde.

Die meisten komponentenbezogenen Meldungen gibt es in zwei Versionen: einer Zusammenfassung (summary) und einer detaillierten Version (detail). 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 detaillierte Meldung. Die andere ist die sogenannte zusammengefasste Meldung. Das Tag <h:message> zeigt standardmäßig die Detailmeldung an. Dieses Verhalten kann durch die Attribute showSummary und showDetail geändert werden. Dies wird für die Komponente mit der ID „saisie2“ getan:


        <!-- Zeile 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}"/>

In Zeile 2 ist die Komponente saisie2 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 zusammengefasste Meldung.

Das Tag <h:messages> zeigt in Form einer Liste alle zusammengefassten Fehlermeldungen aller Komponenten sowie die nicht komponentenbezogenen Fehlermeldungen an. Auch hier können Attribute dieses Standardverhalten ändern:

  • showDetail: true / false, um festzulegen, ob detaillierte Meldungen angefordert werden sollen oder nicht,
  • showSummary: true / false, um festzulegen, ob zusammengefasste Meldungen angezeigt werden sollen oder nicht,
  • 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 werden.

Die mit einer Konvertierung verbundene Fehlermeldung kann auf verschiedene Weise geändert werden. Zunächst kann man der Anwendung mitteilen, eine andere Meldungsdatei zu verwenden. Diese Änderung erfolgt in [faces-config.xml]:


<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, doch diese wird von den Tags <h:message> und <h:messages> nicht verwendet. Zur Definition muss das Tag <message-bundle> in Zeile 9 verwendet werden. Zeile 9 weist die Tags <h:message(s)> an, dass die Datei [messages.properties] vor der Datei [javax.faces.Messages.properties] durchsucht werden soll. Wenn man also die folgenden Zeilen zur Datei [messages_fr.properties] hinzufügt:


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

lautet der für die Komponenten saisie1 und saisie2 zurückgegebene Fehler:

Image

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


        <!-- Zeile 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 bei einem Konvertierungsfehler angezeigt werden soll,
  • Zeile 3: Das Attribut „required=“true“ gibt an, dass die Eingabe obligatorisch ist. Das Feld darf nicht leer bleiben. Ein Feld gilt als leer, wenn es kein Zeichen enthält oder eine Folge von Leerzeichen enthält. Auch hier gibt es in [javax.faces.Messages.properties] eine Standardmeldung:
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.

Mit dem Attribut requiredMessage lässt sich diese Standardmeldung überschreiben. 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

erhält man folgendes Ergebnis:

oder auch dieses:

Es reicht nicht immer aus, zu überprüfen, ob eine Eingabe tatsächlich eine ganze Zahl ist. Manchmal muss geprüft werden, ob die eingegebene Zahl in einem bestimmten Intervall liegt. In diesem Fall wird ein Validator verwendet. Die Eingabe Nr. 4 liefert hierfür ein Beispiel. Der entsprechende Code in [index.xhtml] lautet wie folgt:


        <!-- Zeile 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}"/>

In Zeile 3 ist die Komponente saisie4 mit dem folgenden Modell form.saisie4 verknüpft:


  private Integer saisie4 = 0;

In den Zeilen 3–5 hat das Tag <h:inputText> ein untergeordnetes Tag <f:validateLongRange>, das zwei optionale Attribute „minimum“ und „maximum“ zulässt. Dieses Tag, auch als Validator bezeichnet, ermöglicht es, eine Einschränkung für den Eingabewert festzulegen: Dieser muss nicht nur eine ganze Zahl sein, sondern eine ganze Zahl im Intervall [minimum, maximum], wenn die beiden Attribute minimum und maximum vorhanden sind, größer oder gleich minimum sein, wenn nur das Attribut minimum vorhanden ist, und kleiner oder gleich maximum, wenn nur das Attribut maximum vorhanden ist. Der Validator <f:validateLongRange> enthält 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 Attribut validatorMessage, mit dem eine spezifische Meldung für die Komponente definiert werden kann. So ergibt sich mit dem folgenden Code JSF:


        <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 der folgenden Meldung in [messages.properties]:


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

erhält man das folgende 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 Code XHTML für die Eingaben 5 und 6 lautet wie folgt:


<!-- Zeile 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}"/>
        <!-- Zeile 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 mit den Komponenten saisie5 und saisie6 verknüpft sind:


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

Fehlermeldungen im Zusammenhang mit den Konvertern und Validatoren der Komponenten saisie5 und saisie6, in [messages.properties]:


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

Hier 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. Erfolgt die Eingabe über ein Eingabefeld, wird die Zeichenfolge „true“ in den booleschen Wert „true“ umgewandelt und jede andere Zeichenfolge in den booleschen Wert „false“.

Der Code XHTML aus dem Beispiel:


<!-- Zeile 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 der Komponente saisie7:


  private Boolean saisie7 = true;

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

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

2.8.5.4. Eingabe 8: Eingabe eines Datums

Die Eingabe eines Datums erfolgt im Beispiel mit dem folgenden Code XHTML:


<!-- Zeile 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 java.lang.String <--> 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 ausschließlich in der Richtung java.util.Date --> java.lang.String.

Der Konverter <f:convertDateTime> akzeptiert verschiedene Attribute, darunter das Attribut „pattern“, das die Form der Zeichenkette festlegt, die in ein Datum umgewandelt werden soll, bzw. die Form, in der ein Datum angezeigt werden soll.

Bei der ersten Aufrufe der Seite [index.xhtml] wird die vorstehende Zeile 8 wie folgt angezeigt:

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


  private Date saisie8 = new Date();

wobei saisie8 den Wert des aktuellen Datums annimmt. Der in beiden Fällen für die Anzeige des Datums verwendete Konverter lautet wie folgt:


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

wobei dd (day) die Tagesnummer, MM (Month) die Monatsnummer und yyyy (year) das Jahr bezeichnet. Bei [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] gibt es Standardmeldungen für ungültige Datumsangaben:

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 eigene Meldungen ersetzt werden können. So zum Beispiel im 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>

wird bei einem Konvertierungsfehler die folgende Meldung mit dem Schlüssel saisie8.error angezeigt:


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 festgelegter Länge

Beispiel 9 zeigt, wie man festlegen kann, dass eine eingegebene Zeichenfolge eine bestimmte Zeichenanzahl innerhalb eines bestimmten Bereichs haben muss:


<!-- Zeile 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}"/>

In Zeile 4 schreibt der Validator <f:validateLength minimum="4" maximum="4"/> vor, dass die eingegebene Zeichenfolge genau 4 Zeichen haben muss. Es kann nur eines der Attribute verwendet werden: „minimum“ für eine Mindestanzahl an Zeichen, „maximum“ für eine Höchstanzahl.

Das Template form.saisie9 der Komponente saisie9 aus 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 wie in Zeile 3 oben ersetzt werden können. Die Schlüsselmeldung saisie9.error lautet wie folgt:


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

Hier ein Ausführungsbeispiel:

Image

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

Die Eingabe 9B zeigt, wie man vorschreibt, dass eine eingegebene Zeichenfolge eine bestimmte Zeichenanzahl innerhalb eines bestimmten Bereichs haben muss:


<!-- Zeile 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}"/>

In Zeile 4 schreibt der Validator <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> vor, dass die eingegebene Zeichenfolge dem Muster eines regulären Ausdrucks entsprechen muss, in diesem Fall: eine Folge von 0 oder mehr Leerzeichen, 2 Ziffern, das Zeichen :, 2 Ziffern, eine 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 Standard-Fehlermeldungen:

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 mithilfe des Attributs validatorMessage wie in Zeile 3 oben ersetzt werden können. Die Schlüsselmeldung saisie9.error lautet wie folgt:


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

Hier ein Ausführungsbeispiel:

Image

2.8.5.7. Aufgabe 10: Eine spezifische Validierungsmethode schreiben

Fassen wir zusammen: Mit JSF lassen sich bei den eingegebenen Werten die Gültigkeit von Zahlen (Ganzzahlen, reelle Zahlen) und Datumsangaben, die Länge von Zeichenketten sowie die Übereinstimmung einer Eingabe mit einem regulären Ausdruck überprüfen. Mit JSF können eigene Validatoren und Konverter zu den bereits vorhandenen hinzugefügt werden. Dieser Punkt wird hier nicht behandelt, aber in [ref2] finden Sie weitere Informationen dazu.

Wir stellen hier eine weitere Methode vor: die Validierung einer eingegebenen Datenangabe mithilfe einer Methode des Formularmodells. Dies wird im folgenden Beispiel veranschaulicht:


<!-- Zeile 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}"/>

Das Modell form.saisie10, das der Komponente saisie10 in Zeile 3 zugeordnet ist, sieht wie folgt aus:


  private Integer saisie10 = 0;

Die eingegebene Zahl soll kleiner als 1 oder größer als 7 sein. Dies lässt sich mit den Standard-Validatoren von JSF nicht überprüfen. Daher schreiben wir eine eigene Validierungsmethode für die Komponente saisie10. Diese geben wir über das Attribut „validator“ der zu validierenden Komponente an:


          <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. Diese 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 zwingend der in Zeile 1 entsprechen:

  • FacesContext context: Ausführungskontext der Seite – gewährt Zugriff auf verschiedene Informationen, insbesondere auf die Objekte HttpServletRequest request und HttpServletResponse response,
  • UIComponent component: die zu validierende Komponente. Das Tag <h:inputText> wird durch eine Komponente vom Typ UIInput dargestellt, die von UIComponent abgeleitet ist. Hier wird diese Komponente UIInput als zweiter Parameter übergeben,
  • Objektwert: Der zu überprüfende eingegebene Wert, umgewandelt in den Typ seines Modells. Es ist wichtig zu verstehen, dass die Validierungsmethode nicht ausgeführt wird, wenn die Konvertierung von String in den Modelltyp fehlgeschlagen ist. Wenn die Methode validateSaisie10 aufgerufen wird, bedeutet dies, dass die Konvertierung von String in Integer erfolgreich war. Der dritte Parameter ist dann vom Typ Integer.
  • Zeile 2: Der eingegebene Wert wird in den Typ int, umgewandelt
  • Zeile 3: Es wird überprüft, 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 Ausnahme vom Typ ValidatorException auslöst.

Die Klasse ValidatorException verfügt über zwei Konstruktoren:

  • Der Konstruktor [1] nimmt als Parameter eine Fehlermeldung vom Typ FacesMessage entgegen. Dieser Meldungstyp wird von den Tags <h:messages> und <h:message> angezeigt,
  • der Konstruktor [2] ermöglicht es zudem, die Ursache vom Typ Throwable oder eine vom Fehler abgeleitete Ursache zu kapseln.

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

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

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

Es kann jeder der Konstruktoren verwendet werden, fehlende Parameter können später über die Methoden set festgelegt werden.

Der Konstruktor [1] erlaubt es nicht, eine Meldung anzugeben, die sich in einer internationalisierten Meldungsdatei befindet. Das ist natürlich schade. David Geary und Cay Horstmann [ref2] schließen diese Lücke in ihrem Buch „Core JavaServer Faces“ mit der Hilfsklasse com.corejsf.util.Messages. Diese Klasse wird in Zeile 4 des Java-Codes verwendet, um die Fehlermeldung zu erstellen. Sie enthält ausschließlich 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, jedoch mit dem Paketnamen. Hier könnte unser erster Parameter messages lauten, um die Datei [messages.properties] zu bezeichnen. Bevor die durch den ersten Parameter angegebene Datei verwendet wird, versucht getMessage, die Meldungsdatei der Anwendung zu verwenden, sofern eine vorhanden ist. Wenn also in [faces-config.xml] eine Meldungsdatei mit dem Tag deklariert wurde:

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

kann man null als ersten Parameter an die Methode getMessage übergeben. Genau das wurde hier getan (siehe [web.xm], Seite 120),

  • String resourceId: der Schlüssel der zu verarbeitenden Meldung in der Meldungsdatei. Wir haben gesehen, dass eine Meldung sowohl eine Zusammenfassung als auch eine Detailversion haben kann. resourceId ist die Kennung der Zusammenfassung. Die detaillierte Version wird automatisch anhand des Schlüssels resourceId_detail gesucht. Somit haben wir in [messages.properties] zwei Meldungen für den Fehler bei der Eingabe 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 zusammengefasste als auch die detaillierte Version, sofern diese gefunden wurden. Beide Versionen müssen vorhanden sein, andernfalls wird eine Ausnahme vom Typ [NullPointerException] ausgelöst,

  • Object[] params: die tatsächlichen Parameter der Nachricht, sofern diese formale Parameter {0}, {1}, ... aufweist. Diese formalen Parameter werden durch die Elemente des Arrays params ersetzt.

Kehren wir zum Code der 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 Nachricht vom Typ FacesMessage mithilfe der statischen Methode Messages.getMessage erstellt,
  • in [5] wird der Schweregrad der Meldung festgelegt,
  • in [6] wird eine Ausnahme vom Typ ValidatorException mit der zuvor erstellten Meldung ausgelöst. Die Validierungsmethode wurde durch den folgenden Code XHTML aufgerufen:

<!-- Zeile 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}"/>

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

Hier ein Ausführungsbeispiel:

Image

2.8.5.8. Eingaben 11 und 12: Validierung einer Komponentengruppe

Bisher haben die behandelten Validierungsmethoden jeweils nur eine einzelne Komponente validiert. Wie geht man vor, wenn die gewünschte Validierung mehrere Komponenten betrifft? Das sehen wir uns nun an. Im Formular:

Image

sollen die Eingaben 11 und 12 zwei ganze Zahlen sein, deren Summe 10 ergibt.

Der Code JSF lautet wie folgt:


<!-- Zeile 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}"/>
        <!-- Zeile 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 die zugehörige Vorlage:


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

In Zeile 3 des Codes JSF werden die bereits vorgestellten Techniken verwendet, um zu überprüfen, ob der für die Komponente saisie11 eingegebene Wert tatsächlich eine ganze Zahl ist. Dasselbe gilt in Zeile 11 für die Komponente saisie12. Um zu überprüfen, ob saisie11 + saisie12 = 10 ist, könnte man einen speziellen Validator erstellen. Dies ist die bevorzugte Lösung. Auch hier wird [ref2] ausgewertet, um dies festzustellen. Wir verfolgen hier jedoch einen anderen Ansatz.

Die Seite [index.xhtml] wird über eine Schaltfläche [Valider] validiert, deren Code JSF wie folgt lautet:


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

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


submit=Valider

In Zeile 3 ist zu sehen, dass die Methode form.submit ausgeführt wird, um den Klick auf die Schaltfläche [Valider] zu verarbeiten. Diese lautet wie folgt:


  // Aktionen
  public String submit() {
    // Letzte Bestätigungen
    validateForm();
    // Das gleiche Formular wird zurückgesendet
    return null;
  }

  // Gesamtüberprüfungen
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
...
}

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

  • alle Validatoren und Konverter des Formulars ausgeführt und erfolgreich abgeschlossen wurden,
  • die Felder des Modells [Form.java] die vom Client übermittelten Werte erhalten haben.

Kehren wir nun zum Verarbeitungszyklus eines POST JSF zurück:

Die Methode submit ist ein Ereignisbehandler. Sie verarbeitet das Ereignis clic auf der Schaltfläche [Valider]. Wie alle Ereignisbehandler wird sie in der Phase [E] ausgeführt, nachdem alle Validatoren und Konverter erfolgreich ausgeführt wurden ([C]) und das Modell mit den übermittelten Werten aktualisiert wurde ([D]). Es geht hier also nicht mehr darum, Ausnahmen vom Typ [ValidatorException] auszulösen, wie wir es zuvor getan haben. Wir beschränken uns darauf, das Formular mit Fehlermeldungen zurückzusenden:

In [1] werden wir den Benutzer warnen, und in [2] sowie [3] setzen wir ein Fehlerzeichen. Im Code JSF wird die Meldung [1] wie folgt ermittelt:


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

In Zeile 2 zeigt das Tag <h:messages> standardmäßig die zusammengefasste Version der Fehlermeldungen aller fehlerhaften Eingaben von Formularkomponenten sowie aller nicht komponentenbezogenen Fehlermeldungen an. Das Attribut globalOnly="true" beschränkt die Anzeige auf letztere.

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


<!-- Zeile 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}"/>
        <!-- Zeile 13 -->
        ...
          <h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
        ...

In den Zeilen 4–7 gibt es bei der Komponente saisie11 zwei mögliche Fehlermeldungen:

  • diejenige, die auf eine fehlerhafte Konvertierung oder fehlende Daten hinweist. Diese von JSF selbst generierte Meldung wird in einem Typ FacesMessage enthalten sein und durch den Tag <h:message> in Zeile 5 angezeigt,
  • die Meldung, die wir generieren, wenn Eingabe11 + Eingabe12 nicht gleich 10 ist. Sie wird in Zeile 6 angezeigt. Die Fehlermeldung ist im Template form.errorSaisie11 enthalten.

Die beiden Meldungen beziehen sich auf Fehler, die nicht gleichzeitig auftreten können. Die Überprüfung „Eingabe11 + Eingabe12 = 10“ erfolgt in der Methode submit, die nur ausgeführt wird, wenn im Formular keine Fehler mehr vorhanden sind. Wenn diese Methode ausgeführt wird, wurde die Komponente saisie11 bereits geprüft und ihr Modell form.saise11 hat seinen Wert erhalten. Die Meldung in Zeile 5 kann dann nicht mehr angezeigt werden. Umgekehrt gilt: Wird die Meldung in Zeile 5 angezeigt, liegt mindestens ein Fehler im Formular vor, und die Methode submit wird nicht ausgeführt. Die Meldung in Zeile 6 wird nicht angezeigt. Damit sich die beiden möglichen Fehlermeldungen in derselben Spalte der Tabelle befinden, wurden sie in einem <h:panelGroup>-Tag zusammengefasst (Zeilen 4 und 7).

Die Methode submit lautet wie folgt:


  // Aktionen
  public String submit() {
    // letzte Validierungen
    validateForm();
    // Das gleiche Formular wird erneut gesendet
    return null;
  }

  // globale Validierungen
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
      // globale Meldung
      FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      FacesContext context = FacesContext.getCurrentInstance();
      context.addMessage(null, message);
      // feldbezogene Meldungen
      message = Messages.getMessage(null, "error.sign", null);
      setErrorSaisie11(message.getSummary());
      setErrorSaisie12(message.getSummary());
    } else {
      setErrorSaisie11("");
      setErrorSaisie12("");
    }
}
  • Zeile 4: Die Methode submit ruft die Methode validateForm auf, um die letzten Validierungen durchzuführen,
  • Zeile 11: Es wird geprüft, ob saisie11+saisie12 = 10 ist;
  • ist dies nicht der Fall, so wird in den Zeilen 13–14 eine Nachricht vom Typ FacesMessage mit der Nachrichten-ID saisies11et12.incorrectes erstellt. Diese lautet wie folgt:

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
  • Die so erstellte Meldung wird (Zeilen 15–16) zur Liste der Fehlermeldungen der Anwendung hinzugefügt. Diese Meldung ist nicht an eine bestimmte Komponente gebunden. Es handelt sich um eine globale Meldung der Anwendung. Sie wird durch das oben vorgestellte Tag <h:messages globalOnly="true"/> angezeigt,
  • Zeile 18: Es wird eine neue Meldung vom Typ FacesMessage mit der Meldungs-ID error.sign erstellt. Diese lautet wie folgt:

error.sign="!"

Wir haben bereits erwähnt, dass die statische Methode [Messages.getMessage] eine Nachricht vom Typ FacesMessage mit einer Zusammenfassung und einer Detailansicht erstellt, sofern diese vorhanden sind. In diesem Fall existiert nur die Zusammenfassung der Nachricht error.sign. Die zusammengefasste Version einer Nachricht vom Typ m wird über m.getSummary() abgerufen. In den Zeilen 19 und 20 wird die zusammengefasste Version der Nachricht error.sign in die Felder errorSaisie11 und errorSaisie12 der Vorlage eingefügt. Sie werden durch die folgenden Tags JSF angezeigt:


          <h:outputText value="#{form.saisie11}"/>
          ...
          <h:outputText value="#{form.saisie12}"/>
  • Zeilen 22–23: Wenn die Eigenschaft saisie11+saisie12=10 geprüft wird, werden die beiden Felder errorSaisie11 und errorSaisie12 der Vorlage geleert, damit eine eventuell zuvor angezeigte Fehlermeldung gelöscht wird. Dabei ist zu beachten, dass das Modell zwischen den Anfragen in der Sitzung des Kunden gespeichert bleibt.

Hier ein Ausführungsbeispiel:

In der Spalte [1] ist zu erkennen, dass die Vorlage die übermittelten Werte erhalten hat, was zeigt, dass alle Validierungs- und Konvertierungsvorgänge zwischen den übermittelten Werten und der Vorlage erfolgreich waren. Der Ereignis-Handler form.submit, der den Klick auf die Schaltfläche [Valider] verarbeitet, konnte somit ausgeführt werden. Er hat die in [2] und [3] angezeigten Meldungen erzeugt. Es ist ersichtlich, dass das Modell aktualisiert wurde, obwohl das Formular abgelehnt und an den Kunden zurückgesendet wurde. Man könnte sich wünschen, dass die Vorlage in einem solchen Fall nicht aktualisiert wird. Denn wenn man sich vorstellt, dass der Benutzer die Aktualisierung mit der Schaltfläche [Annuler] [4] abbricht, kann man nicht zur ursprünglichen Vorlage zurückkehren, es sei denn, man hat sie gespeichert.

2.8.5.9. POST eines Formulars ohne Eingabeprüfung

Betrachten wir das obige Formular und nehmen wir an, dass der Benutzer seine Fehler nicht erkennt und die Eingabe des Formulars abbrechen möchte. Er wird dann die Schaltfläche [Annuler] verwenden, die durch den folgenden Code JSF generiert wird:


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

In Zeile 4 lautet die Meldung „msg['cancel']“ wie folgt:


cancel=Annuler

Die Methode form.cancel, die der Schaltfläche [Annuler] zugeordnet ist, wird nur ausgeführt, wenn das Formular gültig ist. Dies haben wir bereits für die Methode form.submit gezeigt, die der Schaltfläche [Valider] zugeordnet ist. Wenn der Benutzer die Eingaben im Formular abbrechen möchte, ist es natürlich sinnlos, die Gültigkeit seiner Eingaben zu überprüfen. Dieses Ergebnis wird mit dem Attribut immediate="true" erzielt, das JSF anweist, die Methode form.cancel auszuführen, ohne die Validierungs- und Konvertierungsphase zu durchlaufen. Kehren wir zum Verarbeitungszyklus von POST und JSF zurück:

Die Ereignisse der Aktionskomponenten <h:commandButton> und <h:commandLink> mit dem Attribut „immediate=“true“ werden in der Phase [C] verarbeitet, anschließend springt der Zyklus JSF direkt zur Phase [E], in der die Antwort gerendert wird.

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 man im vorherigen Formular die Schaltfläche [Annuler] verwendet, erhält man als Ergebnis die folgende Seite:

  • Man erhält erneut das Formular, da der Ereignishandler form.cancel den Navigationsschlüssel null zurückgibt. Die Seite [index.xhtml] wird somit zurückgegeben,
  • das Template [Form.java] wurde durch die Methode form.cancel geändert. Dies spiegelt sich in der Spalte [2] wider, die dieses Template anzeigt,
  • während die Spalte [3] den für die Komponenten gebuchten Wert widerspiegelt.

Kehren wir zum Code JSF der Komponente saisie1 [4] zurück;


          <!-- Zeile 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}"/>

In Zeile 4 ist der Wert der Komponente saisie1 mit dem Modell form.saisie1 verknüpft. Dies hat mehrere Auswirkungen:

  • Bei einem GET von [index.xhtml] zeigt die Komponente saisie1 den Wert der Vorlage form.saisie1 an,
  • bei einem POST von [index.xhtml] wird der für die Komponente saisie1 übermittelte Wert der Vorlage form.saisie1 nur dann zugewiesen, wenn alle Validierungen und Konvertierungen des Formulars erfolgreich sind. Unabhängig davon, ob das Modell durch die übermittelten Werte aktualisiert wurde oder nicht: Wenn das Formular nach Abschluss des POST zurückgesendet wird, zeigen die Komponenten den übermittelten Wert an und nicht den Wert des ihnen zugeordneten Modells. Dies zeigt der obige Screenshot, in dem die Spalten [2] und [3] nicht dieselben Werte aufweisen.

2.9. Beispiel mv-jsf2-07: Ereignisse im Zusammenhang mit der Zustandsänderung von Komponenten JSF

2.9.1. Die Anwendung

Die Anwendung zeigt ein Beispiel für POST, das ohne die Verwendung einer Schaltfläche oder eines Links realisiert wurde. Das Formular sieht wie folgt aus:

Der Inhalt der Liste combo2 [2] ist mit dem im combo1 [1] ausgewählten Element verknüpft. Wenn die Auswahl in [1] geändert wird, wird ein POST des Formulars ausgeführt, bei dem der Inhalt von combo2 so geändert wird, dass er das in [1] ausgewählte Element widerspiegelt; anschließend wird das Formular zurückgegeben. Im Verlauf dieses POST wird keine Validierung durchgeführt.

2.9.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung sieht wie folgt aus:

Es gibt ein einziges Formular [index.xhtml] mit der zugehörigen 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] 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">
  <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">
        <!-- Kopfzeilen -->
        <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"/>
        <!-- Zeile 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}"/>
        <!-- Zeile 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}"/>
        <!-- Zeile 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>
      <!-- Bedienelemente -->
      <h:panelGrid columns="2" border="0">
        <h:commandButton value="#{msg['submit']}"/>
        ...
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

Die Neuerung betrifft den Code der Liste combo1, Zeilen 24–26. Es tauchen neue Attribute auf:

  • onchange: Attribut HTML – deklariert eine Funktion oder JavaScript-Code, der ausgeführt werden soll, wenn sich das in combo1 ausgewählte Element ändert. Hier sendet der JavaScript-Code submit() das Formular an den Server,
  • valueChangeListener: Attribut JSF – gibt den Namen der Methode an, die serverseitig ausgeführt werden soll, wenn sich das in combo1 ausgewählte Element ändert. Insgesamt werden zwei Methoden ausgeführt: eine clientseitig, die andere serverseitig,
  • immediate=true: Attribut JSF – legt fest, wann der serverseitige Ereignisbehandler ausgeführt werden soll: nachdem das Formular entsprechend der Benutzereingabe neu aufgebaut wurde, aber vor den Gültigkeitsprüfungen der Eingaben. Hier soll die Liste combo2 entsprechend dem in der Liste combo1 ausgewählten Element ausgefüllt werden, auch wenn im Formular ansonsten möglicherweise fehlerhafte Eingaben vorhanden sind. Hier ein Beispiel:
  • in [1] eine erste Eingabe,
  • in [2] wird das in combo1 ausgewählte Element von A nach B übertragen.

Das Ergebnis lautet wie folgt:

POST wurde ausgeführt. Der Inhalt von combo2 und [2] wurde an dasausgewählte Element in combo1 und [1] angepasst, obwohl die Eingabe in [3] fehlerhaft war. Das Attribut immediate=true hat dazu geführt, dass die Methode form.combo1ChangeListener vor den Gültigkeitsprü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 abgebrochen worden wäre.

Die folgenden Meldungen sind in [messages.properties] mit dem Formular verknüpft:


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

Die Lebensdauer von [Form.java] ist auf „request“ festgelegt:


package forms;

...

@ManagedBean
@RequestScoped
public class Form {

In Zeile 6 wird der Gültigkeitsbereich des Beans auf „request“ festgelegt.

2.9.5. Das Template [Form.java]

Das Modell [Form.java] lautet wie folgt:


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() {
  }
  
// Formularfelder
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // Arbeitsfelder
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // Methoden
  public SelectItem[] getCombo1Items(){
    // combo1 initialisieren
    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(){
    // combo2 in Abhängigkeit von combo1 initialisieren
    SelectItem[] combo2Items=new SelectItem[5];
    for(int i=1;i<=combo2Items.length;i++){
      combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
    }
    return combo2Items;
  }
  
  // Listener
  public void combo1ChangeListener(ValueChangeEvent event){
    // Verfolgung
    logger.info("combo1ChangeListener");
    // Der über das Formular übermittelte Wert von combo1 wird abgerufen
    combo1Label=(String)event.getNewValue();
    // Die Antwort wird zurückgegeben, da die Validierungen umgangen werden sollen
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // Weiter
    logger.info("raz");
    // Formular zurücksetzen
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
  
// Getter – Setter
  ...
}

Verknüpfen wir das Formular [index.xhtml] mit seinem Modell [Form.java]:

Die Liste combo1 wird durch den folgenden Code JSF generiert:


        <h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
          <f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>

Sie bezieht ihre Elemente über die Methode getCombo1Items ihrer Vorlage (Zeile 2). Diese 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 Code JSF generiert:


        <h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
          <f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>

Sie bezieht ihre Elemente über die Methode getCombo2Items ihres Modells (Zeile 2). Diese 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. Bei der erstmaligen Generierung des Formulars enthält die Liste combo2 also die Elemente {"A1","A2", „A3“, „A4“, „A5“.

Wenn der Benutzer das ausgewählte Element in der Liste „combo1“ ändert,

  • wird das Ereignis onchange="submit();" vom Client-Browser verarbeitet. Das Formular wird somit an den Server gesendet,
  • auf der Serverseite erkennt JSF, dass sich der Wert der Komponente combo1 geändert hat. Die Methode combo1ChangeListener in den Zeilen 47–54 wird ausgeführt. Eine Methode vom Typ ValueChangeListener erhält als Parameter ein Objekt vom Typ javax.faces.event.ValueChangeEvent. Mit diesem Objekt lassen sich der alte und der neue Wert der Komponente, deren Wert sich geändert hat, mithilfe der folgenden Methoden abrufen:

Image

Hierbei handelt es sich bei der Komponente um 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 der Liste combo2 dient,
  • Zeile 53: Die Antwort wird zurückgegeben. Dabei ist zu beachten, dass der Handler combo1ChangeListener mit dem Attribut immediate="true" ausgeführt wird. Er wird also nach der Phase ausgeführt, in der der Komponentenbaum der Seite mit den übermittelten Werten aktualisiert wurde, und vor dem Validierungsprozess der übermittelten Werte. Dieser Validierungsprozess soll jedoch vermieden werden, da die Liste combo2 aktualisiert werden muss, auch wenn im Formular ansonsten noch fehlerhafte Eingaben vorhanden sind. Daher wird verlangt, dass die Antwort sofort gesendet wird, ohne die Phase der Eingabevalidierung zu durchlaufen.
  • Das Formular wird so zurückgesendet, wie es eingegeben wurde. Die Elemente der Listen combo1 und combo2 sind jedoch keine übermittelten Werte. Sie werden durch Aufruf der Methoden getCombo1Items und getCombo2Items neu generiert. Diese letzte Methode verwendet dann den neuen Wert von combo1Label, der durch combo1ChangeListener festgelegt wurde, und die Elemente der Liste combo2 ändern sich.

2.9.6. Die Schaltfläche [Raz]

Mit der Schaltfläche [Raz] möchten wir das Formular in den ursprünglichen Zustand zurückversetzen, wie unten dargestellt:

In [1] befindet sich das Formular vor dem POST der Schaltfläche [Raz], in [2] das Ergebnis von POST.

Obwohl funktional einfach, erweist sich die Umsetzung dieses Anwendungsfalls als recht komplex. Man kann verschiedene Lösungen ausprobieren, insbesondere diejenige, die für die Schaltfläche [Annuler] aus dem vorherigen Beispiel verwendet wurde:


       <h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>

wobei die Methode form.raz wie folgt lautet:


  public String raz(){
    // Formular zurücksetzen
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
}

Das Ergebnis, das mit der Schaltfläche [Raz] im vorherigen Beispiel erzielt wird, lautet dann wie folgt:

Die Spalte „[1]“ zeigt an, dass die Methode „form.raz“ ausgeführt wurde. Die Spalte „[1]“ zeigt jedoch weiterhin die gebuchten Werte an:

  • Für combo1 lautete der gebuchte Wert „B“. Dieses Element ist daher in der Liste ausgewählt,
  • für combo2 lautete der gebuchte Wert „B5“. Aufgrund der 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. In diesem Fall wird das erste Element der Liste angezeigt,
  • für saisie1 betrug der gebuchte Wert 10.

Dies ist das normale Verhalten mit dem Attribut immediate=&quot;true&quot;. Um ein anderes Ergebnis zu erzielen, müssen die gewünschten Werte in das neue Formular übermittelt werden, auch wenn der Benutzer andere Werte eingegeben hat. Dies lässt sich mit etwas clientseitigem JavaScript-Code bewerkstelligen. 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['formulaire'].submit();
  }
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
  • Zeile 10: Das Attribut `onclick='raz()'` weist an, die JavaScript-Funktion `raz` auszuführen, wenn der Benutzer auf die Schaltfläche `[Raz]` klickt.
  • Zeile 3: Dem Element HTML mit dem Namen „formulaire:combo1“ wird der Wert „A“ zugewiesen. Die verschiedenen Elemente in Zeile 3 sind folgende:
    • document: vom Browser angezeigte Seite,
    • document.forms: Gesamtheit der Formulare des Dokuments,
    • document.forms['formulaire']: das Formular mit dem Attribut name="formulaire",
    • documents.forms['formulaire'].elements: Gesamtheit der Formularelemente mit dem Attribut name="formulaire",
    • document.forms['formulaire'].elements['formulaire:combo1']: Formularelement mit dem Attribut name="formulaire:combo1"
    • document.forms['formulaire'].elements['formulaire:combo1'].value: Wert, der von dem Formularelement mit dem Attribut name="formulaire:combo1". übermittelt wird

Um die Attribute name der verschiedenen Elemente der vom Browser angezeigten Seite zu ermitteln, kann man deren Quellcode einsehen (unten mit IE7):

<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>

Vor diesem Hintergrund wird verständlich, dass im JavaScript-Code der Funktion raz:

  • Zeile 3 bewirkt, dass der für die Komponente combo1 übermittelte Wert die Zeichenkette A ist,
  • Zeile 4 bewirkt, dass der an die Komponente combo2 übergebene Wert die Zeichenkette A1 ist,
  • Zeile 5 bewirkt, dass der an die Komponente saisie1 übermittelte Wert die Zeichenfolge 0 ist.

Danach wird die Komponente POST des Formulars, die jeder Schaltfläche vom Typ <h:commandButton> (Zeile 10) zugeordnet ist, ausgeführt. Die Methode form.raz wird ausgeführt und das Formular so zurückgegeben, wie es übermittelt wurde. Man erhält dann folgendes Ergebnis:

Hinter diesem Ergebnis verbergen sich viele Details. Die Werte „A“, „A1“ und „0“ der Komponenten combo1, combo2 und saisie1 werden an den Server gesendet. Nehmen wir an, der vorherige Wert von combo1 war „B“. Dann liegt eine Wertänderung der Komponente combo1 vor, und die Methode form.combo1ChangeListener müsste ebenfalls ausgeführt werden. Es gibt 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 {
  
...  
// Formularfelder
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // Arbeitsfelder
  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){
    // Verfolgung
    logger.info("combo1ChangeListener");
    // Der über das POST-Formular übermittelte Wert von combo1 wird abgerufen
    combo1Label=(String)event.getNewValue();
    // Die Antwort wird zurückgegeben, da die Validierungen umgangen werden sollen
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // Weiter
    logger.info("raz");
    // Formular zurücksetzen
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
...
}
  • Zeile 16: Ein Protokollgenerator wird erstellt. Der Parameter von getLogger ermöglicht es, die Herkunft der Protokolle zu unterscheiden. Hier heißt der Protokollierer forms.Form,
  • Zeile 21: Der Aufruf der Methode combo1ChangeListener wird protokolliert,
  • Zeile 30: Der Aufruf der Methode raz wird protokolliert.

Welche Protokolleinträge werden durch die Schaltfläche „[Raz]“ oder durch die Wertänderung von „combo1“ erzeugt? Betrachten wir verschiedene Fälle:

  • Die Schaltfläche [Raz] wird verwendet, während das in combo1 ausgewählte Element „A“ ist. „A“ ist somit der letzte Wert der Komponente combo1. Wir haben gesehen, dass die Schaltfläche [Raz] eine JavaScript-Funktion ausführt, die den Wert „A“ für die Komponente combo1 übermittelt. Der Wert der Komponente combo1 ändert sich daher nicht. Die Protokolle zeigen nun, dass nur die Methode form.raz ausgeführt wird:
  
  • Die Schaltfläche [Raz] wird verwendet, obwohl das in combo1 ausgewählte Element nicht „A“ ist. Die Komponente combo1 ändert daher ihren Wert: Ihr letzter Wert war nicht „A“, und die Schaltfläche [Raz] übermittelt ihr den Wert „A“. Die Protokolle zeigen dann, dass zwei Methoden ausgeführt werden. In dieser Reihenfolge: combo1ChangeListener, raz:
  
  • Wir ändern den Wert von combo1, ohne die Schaltfläche [Raz] zu verwenden. Die Protokolle zeigen, dass nur die Methode combo1ChangeListener ausgeführt wird:
  

2.10. Beispiel mv-jsf2-08: das Tag <h:dataTable>

2.10.1. Die Anwendung

Die Anwendung zeigt eine Liste von Personen an und bietet die Möglichkeit, diese zu löschen:

  • in [1] eine Liste von Personen,
  • in [2] die Links, über die sie gelöscht werden können.

2.10.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung lautet wie folgt:

Es gibt ein einziges Formular [index.xhtml] mit der zugehörigen 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 Meldungsdatei [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]

Zur Erinnerung: Die mit der Seite [index.xhtml] verknüpfte Ansicht:

  

Das Formular [index.xhtml] 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">
  <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>

In Zeile 14 verwendet das Tag <h:dataTable> das Feld #{form.personnes} als Datenquelle. Es sieht wie folgt aus:

private List<Person> Personen;

Die Klasse [Personne] lautet wie folgt:


package forms;

public class Personne {
  // Daten
  private int id;
  private String nom;
  private String prénom;
  
  // Hersteller
  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 und Setter
...
}

Kehren wir zum Inhalt des Tags <h:dataTable> zurück:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
  • Das Attribut var="personne" legt den Namen der Variablen fest, die die aktuelle Person innerhalb des Tags <h:datatable> repräsentiert,
  • das Attribut headerClass="headers" legt den Stil der Spaltenüberschriften der Tabelle fest,
  • Das Attribut columnClasses="...." legt den Stil jeder einzelnen Spalte der Tabelle fest.

Betrachten wir eine der Spalten der Tabelle und sehen wir uns an, wie sie aufgebaut ist:

  

Der Code XHTML der 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 Spalte „id“ der Tabelle,
  • Zeilen 8–13: die Spalte „name“ der Tabelle,
  • Zeilen 14–19: die Spalte „Vorname“ der Tabelle.

Betrachten wir nun die Spalte „Links“ [Retirer]:

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 „[Retirer]“ wird durch die Zeilen 4–6 generiert. Wenn der Link angeklickt wird, wird die Methode „[Form].retirerPersonne“ ausgeführt. Nun wollen wir uns die Klasse [Form.java] genauer ansehen:


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 {

  // Modell
  private List<Personne> personnes;
  private int personneId;

  // Konstruktor
  public Form() {
    // Initialisierung der Personenliste
    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() {
    // Suche nach der ausgewählten Person
    int i = 0;
    for (Personne personne : personnes) {
      // Aktuelle Person = ausgewählte Person?
      if (personne.getId() == personneId) {
        // Die aktuelle Person wird aus der Liste gelöscht
        personnes.remove(i);
        // Vorgang abgeschlossen
        break;
      } else {
        // Nächste Person
        i++;
      }
    }
    // Auf derselben Seite testen
    return null;
  }
  
  // Getter und Setter
...
}
  • Zeilen 18–24: Der Konstruktor initialisiert die Liste der Personen aus Zeile 14,
  • Zeile 10: Da diese Liste über mehrere Abfragen hinweg bestehen bleiben muss, ist der Gültigkeitsbereich der Bean die Sitzung.

Wenn die Methode [retirerPersonne] in Zeile 26 ausgeführt wird, wurde das Feld in Zeile 15 mit der ID der Person initialisiert, deren Link [Retirer] angeklickt wurde:


          <h:commandLink value="Retirer" action="#{form.retirerPersonne}">
            <f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>

Mit dem Tag <f:setPropertyActionListener> können Informationen an das Modell übergeben werden. Hier wird der Wert des Attributs „value“ in das Feld des Modells kopiert, das durch das Attribut „target“ identifiziert wird. So wird die ID der aktuellen Person, die aus der Personenliste gelöscht werden soll, über den Getter dieses Feldes in das Feld [Form].personneId kopiert. Dies geschieht vor der Ausführung der Methode, auf die das Attribut „action“ in Zeile 1 verweist.

Zeilen 26–43: Die Methode [supprimerPersonne] löscht die Person, deren id gleich personneId ist.

2.11. Beispiel „ “ mv-jsf2-09: Layout einer Anwendung JSF

2.11.1. Die Anwendung

Die Anwendung zeigt, wie das Layout einer Anwendung mit zwei Ansichten gestaltet wird:

Die Anwendung verfügt über zwei Ansichten:

  • in [1], Seite 1,
  • in [2], Seite 2.

Man kann zwischen den beiden Seiten navigieren. Hier soll gezeigt werden, dass die Seiten 1 und 2 ein gemeinsames Layout haben, wie auf den obigen Screenshots zu sehen ist.

2.11.2. Das NetBeans-Projekt

Das NetBeans-Projekt der Anwendung lautet wie folgt:

Die Anwendung besteht ausschließlich aus Seiten vom Typ XHTML. Es gibt keine zugehörige Java-Vorlage.

2.11.3. Die Seite [layout.xhtml]

Die Seite [layout.xhtml] legt das Layout der Seiten der Anwendung fest:


<?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 Namensraum „ui“. Dieser Namensraum enthält die Tags, mit denen die Seiten einer Anwendung formatiert werden können. Die Tags dieses Namensraums werden in den Zeilen 17, 22, 25 und 32 verwendet.

Die Seite [layout.xhtml] zeigt Informationen in einer Tabelle HTML an (Zeile 14). Diese Seite kann mit einem Browser aufgerufen werden:

  • in [1], die angeforderte URL.

Das Feld [2] wurde durch den folgenden Code XHTML 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 Tag <ui:include> in Zeile 6 ermöglicht es, einen externen Code XHTML in die Seite einzubinden. Die Datei [entete.xhtml] 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">
  <body>
    <h2>entête</h2>
  </body>
</html>

Der gesamte Code der Zeilen 3–8 wird in [layout.xhtml] eingefügt. Somit werden die Tags <html> und <body> in ein <td>-Tag eingefügt. Dies führt zu keinen Fehlern. Die mit <ui:include> eingefügten Seiten sind also vollständige XHTML-Seiten. Optisch wird sich nur Zeile 6 auswirken. Die Tags <html> und <body> sind aus syntaktischen Gründen vorhanden.

Der Bereich [3] wurde durch den folgenden Code XHTML 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 Tag <ui:include> 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>

Der Bereich [4] wurde durch den folgenden Code XHTML 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>

Der Bereich [5] wurde durch den folgenden Code XHTML generiert:


    <h:form id="formulaire">
...
          <td>
            <ui:insert name="contenu" >
              <h2>Contenu</h2>
            </ui:insert>
          </td>
 ...
      </table>
</h:form>

Das Tag <ui:insert> in Zeile 5 definiert einen Bereich namens „Inhalt“. 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 namens „Inhalt“ kein Inhalt definiert. In diesem Fall wird der Inhalt des Tags <ui:insert> aus 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]. Man spricht hier von Seitenvorlagen. 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 Namensraum „ui“ verwendet,
  • in Zeile 7 wird mithilfe eines <ui:composition>-Tags angegeben, dass die Seite der Vorlage [layout.xhtml] zugeordnet ist,
  • Zeile 8: Durch diese Zuordnung wird jedes <ui:define>-Tag einem <ui:insert>-Tag der verwendeten Vorlage zugeordnet, in diesem Fall [layout.xhtml]. Die Verknüpfung erfolgt über das „name“-Attribut beider Tags. 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 der angeforderten Seite ersetzt wird. Hier verhält es sich so, als wäre die angezeigte Seite:


<?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 Tags <ui:insert> von [layout.xml] eingefügt.

Die Seite [page2.xhtml] entspricht der Seite [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 soeben durchgeführte Untersuchung von JSF 2 ist bei weitem nicht vollständig. Sie reicht jedoch aus, um die folgenden Beispiele zu verstehen. Für weitere Informationen lesen Sie bitte [ref2].

2.13. Tests mit Eclipse

Wir zeigen nun, wie man Maven-Projekte mit der SpringSource Tool Suite testet:

  • In [1] importieren wir ein Maven-Projekt [2], das wir über die Schaltfläche [3] auswählen. Wir verwenden hier das Maven-Projekt [mv-jsf2-09] für Eclipse
  • in [4] wurde das importierte Projekt korrekt als Maven-Projekt [5] erkannt,
  • in [6], das in den Projekt-Explorer importierte Projekt,
  • in [7] wird es auf einem Tomcat-Server ausgeführt [8] [9],
  • in [10] wurde Tomcat 7 gestartet,
  • in [11] wird die Startseite des Projekts [mv-jsf2-09] [11] in einem Eclipse-internen Browser angezeigt.