Skip to content

2. Java Server Faces

We stellen nu het Java Server Faces-framework voor. We zullen versie 2 gebruiken, maar de voorbeelden tonen voornamelijk kenmerken van versie 1. Van versie 2 zullen we alleen de kenmerken bespreken die nodig zijn voor de voorbeeldtoepassing die hierna volgt.

2.1. De rol van JSF in een webapplicatie

Laten we allereerst de plaats van JSF binnen de ontwikkeling van een webapplicatie in kaart brengen. Meestal wordt deze gebouwd op basis van een meerlaagse architectuur, zoals de volgende:

  • de laag [web] is de laag die in contact staat met de gebruiker van de webapplicatie. De gebruiker communiceert met de webapplicatie via webpagina’s die in een browser worden weergegeven. In deze laag bevindt zich JSF en uitsluitend in deze laag,
  • De laag [métier] implementeert de bedrijfsregels van de applicatie, zoals de berekening van een salaris of een factuur. Deze laag maakt gebruik van gegevens afkomstig van de gebruiker via de laag [web] en van het DBMS via de laag [DAO],
  • de laag [DAO] (Data Access Objects), de laag [jpa] (Java Persistence API) en de driver JDBC beheren de toegang tot de gegevens van het DBMS. De laag [jpa] fungeert als ORM (Object Relational Mapper). Deze vormt een brug tussen de objecten die door de laag [DAO] worden beheerd en de rijen en kolommen van de gegevens in een relationele database,
  • de integratie van de lagen kan worden gerealiseerd door een Spring-container of EJB3 (Enterprise Java Bean).

De voorbeelden die hierna worden gegeven om JSF te illustreren, maken slechts gebruik van één laag, namelijk de [web]-laag:

Zodra de basisprincipes van JSF onder de knie zijn, gaan we meerlaagse Java-toepassingen EE bouwen.

2.2. Het ontwikkelingsmodel MVC van JSF

JSF implementeert het zogenaamde MVC-architectuurmodel (Model – View – Controller) op de volgende manier:

Deze architectuur implementeert het ontwerppatroon MVC (Model, View, Controller). De verwerking van een verzoek van een client verloopt volgens de volgende vier stappen:

  1. verzoek – de browser van de klant doet een verzoek aan de controller [Faces Servlet]. Deze verwerkt alle verzoeken van klanten. Dit is de toegangspoort tot de applicatie. Dit is de C van MVC,
  2. verwerking – de C-controller verwerkt dit verzoek. Hierbij wordt hij bijgestaan door gebeurtenishandlers die specifiek zijn voor de geschreven applicatie [2a]. Deze handlers kunnen de hulp nodig hebben van de bedrijfslaag [2b]. Zodra het verzoek van de klant is verwerkt, kan dit verschillende reacties oproepen. Een klassiek voorbeeld is:
    • een foutpagina als het verzoek niet correct kon worden verwerkt;
    • een bevestigingspagina in het andere geval,
  3. navigatie – de controller kiest de reactie (= weergave) die naar de klant moet worden verzonden. Het kiezen van de reactie die naar de klant moet worden verzonden, omvat verschillende stappen:
    • het Facelet kiezen dat het antwoord zal genereren. Dit wordt de weergave V genoemd, de V van MVC. Deze keuze hangt doorgaans af van het resultaat van de uitvoering van de door de gebruiker gevraagde actie;
    • deze Facelet voorzien van de gegevens die hij nodig heeft om dit antwoord te genereren. Dit antwoord bevat namelijk meestal informatie die door de controller is berekend. Deze informatie vormt wat men het M-model van de weergave noemt, de M van MVC,

Stap 3 bestaat dus uit het kiezen van een weergave V en het opstellen van het daarvoor benodigde model M.

  1. antwoord – de controller C vraagt de gekozen Facelet om zichzelf weer te geven. Deze gebruikt het door de controller C voorbereide model M om de dynamische delen van het antwoord te initialiseren dat zij naar de client moet sturen. De exacte vorm hiervan kan variëren: het kan een stream HTML, PDF, Excel, ... zijn

In een project JSF:

  • is de C-controller de servlet [javax.faces.webapp.FacesServlet]. Deze bevindt zich in de bibliotheek [javaee.jar],
  • de V-weergaven worden geïmplementeerd door pagina's die gebruikmaken van de Facelets-technologie,
  • de modellen M en de gebeurtenishandlers worden geïmplementeerd door Java-klassen die vaak „backing beans” of kortweg „beans” worden genoemd.

Laten we nu het verband tussen de webarchitectuur MVC en de gelaagde architectuur verduidelijken. Dit zijn twee verschillende concepten die soms door elkaar worden gehaald. Laten we een webapplicatie JSF met één laag nemen:

Als we de laag [web] implementeren met JSF, hebben we weliswaar een webarchitectuur MVC, maar geen meerlaagse architectuur. Hier zorgt de laag [web] voor alles: presentatie, bedrijfslogica, toegang tot gegevens. Bij JSF zijn het de beans die dit werk doen.

Laten we nu eens kijken naar een meerlaagse webarchitectuur:

De laag [web] kan worden geïmplementeerd zonder framework en zonder het model MVC te volgen. We hebben dan wel een meerlaagse architectuur, maar de weblaag implementeert het model MVC niet.

In MVC hebben we gezegd dat het model M dat van de weergave V is, c.a.d. De verzameling gegevens die door de weergave V wordt weergegeven. Er wordt vaak een andere definitie van het model M van MVC gegeven:

Veel auteurs zijn van mening dat wat zich rechts van de laag [web] bevindt, het model M van MVC vormt. Om dubbelzinnigheden te voorkomen, spreken we van:

  • het domeinmodel wanneer we alles bedoelen wat rechts van de laag [web] staat,
  • het model van de weergave wanneer we verwijzen naar de gegevens die door een weergave V worden getoond.

In het vervolg zal de term „M-model” uitsluitend verwijzen naar het model van een weergave V.

2.3. Voorbeeld mv-jsf2-01: de elementen van een project JSF

De eerste voorbeelden zullen beperkt blijven tot de enige weblaag die is geïmplementeerd met JSF 2:

Zodra de basisbeginselen onder de knie zijn, zullen we complexere voorbeelden met meerlaagse architecturen bestuderen.

2.3.1. Het project genereren

We genereren ons eerste project JSF2 met NetBeans 7.

  
  • in [1], maak je een nieuw project aan,
  • in [2], de categorie [Maven] en het projecttype [Web Application] kiezen,
  • in [3], de bovenliggende map van de map van het nieuwe project aangeven,
  • in [4], geef het project een naam,
  • in [5], kies je een server. Met NetBeans 7 kun je kiezen tussen de servers Apache Tomcat en GlassFish. Het verschil tussen beide is dat GlassFish EJB (Enterprise Java Bean) ondersteunt en Tomcat niet. Onze JSF-voorbeelden maken geen gebruik van EJB. Hier kun je dus elke server kiezen,
  • in [6] kiezen we de Java-versie EE 6 Web,
  • in [7] het gegenereerde project.

Laten we de onderdelen van het project eens bekijken en de rol van elk onderdeel toelichten.

  • in [1]: de verschillende takken van het project:
    • [Web Pages]: bevat de webpagina’s (.xhtml, .jsp, .html), de bronnen (afbeeldingen, diverse documenten), de configuratie van de weblaag en die van het framework JSF;
    • [Source packages]: de Java-klassen van het project;
    • [Dependencies]: de .jar-archieven die nodig zijn voor het project en die worden beheerd door het Maven-framework;
    • [Java Dependencies]: de .jar-bestanden die nodig zijn voor het project en niet worden beheerd door het Maven-framework;
    • [Project Files]: configuratiebestand voor Maven en NetBeans,
  • in [2]: de branch [Web Pages],

Deze bevat de volgende pagina [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>

Dit is een webpagina waarop de tekenreeks 'Hello World' in grote letters wordt weergegeven.

Het bestand [META-INF/context.xml] is als volgt:


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

Regel 2 geeft aan dat de applicatiecontext (of de naam ervan) /mv-jsf2-01 is. Dit betekent dat de webpagina's van het project zullen worden opgevraagd via een URL in de vorm http://machine:port/mv-jsf2-01/page. De context is standaard de naam van het project. We hoeven dit bestand niet te wijzigen.

  • in [3], de tak [Source Packages],

Deze branch bevat de broncode van de Java-klassen van het project. Hier bevinden zich geen klassen. NetBeans heeft een standaardpakket gegenereerd dat kan worden verwijderd: [4].

  • in [5], de tak [Dependencies],

Deze tak toont alle bibliotheken die nodig zijn voor het project en die door Maven worden beheerd. Alle hier vermelde bibliotheken worden automatisch door Maven gedownload. Daarom heeft een Maven-project internettoegang nodig. De gedownloade bibliotheken worden lokaal opgeslagen. Als een ander project een bibliotheek nodig heeft die al lokaal aanwezig is, wordt deze niet opnieuw gedownload. We zullen zien dat deze lijst met bibliotheken en de repositories waar ze te vinden zijn, worden gedefinieerd in het configuratiebestand van het Maven-project.

  • in [6], de bibliotheken die nodig zijn voor het project en die niet door Maven worden beheerd,
  • in [7], de configuratiebestanden van het Maven-project:
    • [nb-configuration.xml] is het configuratiebestand van NetBeans. Daar zullen we niet op ingaan.
    • [pom.xml]: het configuratiebestand van Maven. POM staat voor Project Object Model. Soms zal het nodig zijn om dit bestand rechtstreeks te bewerken.

Het gegenereerde bestand [pom.xml] ziet er als volgt uit:


<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>
  • de regels 5-8 definiëren het Java-object (artefact) dat door het Maven-project zal worden aangemaakt. Deze informatie is afkomstig van de wizard die bij het aanmaken van het project is gebruikt:

Een Maven-object wordt gedefinieerd door vier eigenschappen:

  • [groupId]: informatie die lijkt op een pakketnaam. Zo hebben de bibliotheken van het Spring-framework groupId=org.springframework, die van het QZXW2HTML-framework JSF hebben groupId=javax.faces,
  • [artifactId]: de naam van het Maven-object. In de groep [org.springframework] vinden we dus de volgende artifactId: spring-context, spring-core, spring-beans, ... In de groep [javax.faces] bevinden zich de artifactId en jsf-api,
  • [version]: versienummer van het Maven-artefact. Het artefact org.springframework.spring-core heeft dus de volgende versies: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
  • [packaging]: de vorm die het artefact aanneemt, meestal war of jar.

Ons Maven-project zal dus een [war] (regel 8) genereren in de groep [istia.st] (regel 5), met de naam [mv-jsf2-01] (regel 6) en versie [1.0-SNAPSHOT] (regel 7). Deze vier gegevens moeten een Maven-artefact eenduidig definiëren.

De regels 17-24 geven een overzicht van de afhankelijkheden van het Maven-project, dat wil zeggen de lijst met bibliotheken die nodig zijn voor het project. Elke bibliotheek wordt gedefinieerd door de vier gegevens (groupId, artifactId, versie, packaging). Wanneer het gegeven packaging ontbreekt, zoals hier, wordt de packaging-jar gebruikt. Daarnaast wordt nog een gegeven toegevoegd, namelijk ‘scope’, dat bepaalt op welke momenten in de levenscyclus van het project de bibliotheek nodig is. De standaardwaarde is ‘compile’, wat aangeeft dat de bibliotheek nodig is voor zowel de compilatie als de uitvoering. De waarde ‘provided’ betekent dat de bibliotheek nodig is tijdens de compilatie, maar niet tijdens de uitvoering. Hier wordt de bibliotheek tijdens de uitvoering geleverd door de Tomcat 7-server.

2.3.2. Het project uitvoeren

We voeren het project uit:

In [1] wordt het Maven-project uitgevoerd. De Tomcat-server wordt dan gestart, als dat nog niet het geval was. Er wordt ook een browser gestart en de pagina URL uit de projectcontext wordt opgevraagd: [2]. Aangezien er geen document wordt opgevraagd, wordt de pagina index.html, index.jsp, index.xhtml gebruikt, indien deze bestaat. In dit geval is dat de pagina [index.jsp].

2.3.3. Het bestandssysteem van een Maven-project

  • [1]: het bestandssysteem van het project bevindt zich in het tabblad [Files],
  • [2]: de Java-broncodes bevinden zich in de map [src / main / java],
  • [3]: de webpagina's bevinden zich in de map [src / main / webapp],
  • [4]: de map [target] wordt aangemaakt tijdens het bouwen (build) van het project,
  • [5]: hier heeft de build van het project een archief met de naam [mv-jsf2-01-1.0-SNAPSHOT.war] aangemaakt. Dit archief is door de Tomcat-server uitgevoerd.

2.3.4. Een project configureren voor JSF

Ons huidige project is geen JSF-project. De bibliotheken van het JSF-framework ontbreken. Om van het huidige project een JSF-project te maken, gaan we als volgt te werk:

  • naar [1], open je de projecteigenschappen,
  • in [2] kiest men de categorie [Frameworks],
  • in [3] voeg je een framework toe,
  • In [4] kiezen we voor Java Server Faces,
  • in [5] stelt NetBeans versie 2.1 van het framework voor. We accepteren dit,
  • in [6] wordt het project vervolgens uitgebreid met nieuwe afhankelijkheden.

Het bestand [pom.xml] is aangepast om deze nieuwe configuratie weer te geven:


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

Regels 14-33: er zijn nieuwe afhankelijkheden toegevoegd. Maven downloadt deze automatisch. Het haalt ze op uit zogenaamde repositories. De centrale repository (Central Repository) wordt automatisch gebruikt. We kunnen andere repositories toevoegen met behulp van de tag <repository>. Hier zijn twee repositories toegevoegd:

  • regels 46-51: een repository voor de bibliotheek JSF 2,
  • regels 52-57: een repository voor de bibliotheek JSTL 1.1.

Het project is ook uitgebreid met een nieuwe webpagina:

De pagina [index.HTML] ziet er als volgt uit:


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

Hier zien we een bestand XML (regel 1). Daarin staan de tags van HTML, maar dan in het formaat XML. Dit wordt XHTML genoemd. De technologie die wordt gebruikt om webpagina’s te maken met JSF 2 heet Facelets. Daarom wordt de pagina XHTML soms ook wel een Facelet-pagina genoemd.

De regels 3-4 definiëren de tag <html> met de naamruimte XML (xmlns=XML Name Space).

  • regel 3 definieert de hoofdnaamruimte http://www.w3.org/1999/xhtml,
  • regel 4 definieert de naamruimte http://java.sun.com/jsf/html voor de tags HTML. Deze krijgen het voorvoegsel h: zoals aangegeven door xmlns:h. Deze tags zijn te vinden op de regels 5, 7, 8 en 10.

Bij het tegenkomen van een naamruimteverklaring zal de webserver de mappen [META-INF] en Classpath van de applicatie doorzoeken, op zoek naar bestanden met de extensie .tld (TagLib Definition). Hier vindt hij ze in het archief [jsf-impl.jar] [1,2]:

Laten we het bestand [3] en het bestand [HTML_basic.tld] eens bekijken:

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

<!-- ============== Beschrijving elementen tagbibliotheek ============= -->

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

<!-- ============== Tagbibliotheekvalidator ============= -->
...
  • op regel 19, de URI van de tagbibliotheek,
  • op regel 16 de korte naam ervan.

De definities van de verschillende <h:xx>-tags zijn in dit bestand te vinden. Deze tags worden beheerd door Java-klassen die ook in het artefact [jsf-impl.jar] te vinden zijn.

Laten we teruggaan naar ons project JSF. Er is een nieuwe branch aan toegevoegd:

De branch [Other Sources] [1] bevat de bestanden die in het classpath van het project moeten staan en die geen Java-code zijn. Dit geldt bijvoorbeeld voor de berichtbestanden in JSF. We hebben gezien dat deze tak ontbreekt als het JSF-framework niet aan het project is toegevoegd. Om deze aan te maken, volstaat het om de map [src / main / resources] [3] aan te maken in het tabblad [Files] [2].

Ten slotte is er een nieuwe map verschenen in de tak [Web Pages]:

De map [WEB-INF] is aangemaakt met daarin het bestand [web.xml] . Dit bestand configureert de webapplicatie:


<?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>
  • de regels 7-10 definiëren een servlet, c.a.d, een Java-klasse die verzoeken van clients kan verwerken. Een applicatie JSF werkt als volgt:

Deze architectuur implementeert het ontwerppatroon MVC (Model, View, Controller). We herhalen wat hierboven al is beschreven. De verwerking van een verzoek van een client verloopt volgens de volgende vier stappen:

1 - verzoek - de browser van de klant doet een verzoek aan de controller [Faces Servlet]. Deze verwerkt alle verzoeken van klanten. Dit is de toegangspoort tot de applicatie. Dit is de C van MVC,

2 - verwerking - de C-controller verwerkt dit verzoek. Hierbij wordt hij bijgestaan door gebeurtenishandlers die specifiek zijn voor de geschreven applicatie [2a]. Deze handlers kunnen de hulp nodig hebben van de bedrijfslaag [2b]. Zodra het verzoek van de klant is verwerkt, kan dit verschillende reacties oproepen. Een klassiek voorbeeld is:

  • een foutpagina als het verzoek niet correct kon worden verwerkt;
  • een bevestigingspagina in het tegenovergestelde geval,

3 - navigatie - de controller kiest het antwoord (= weergave) dat naar de klant moet worden verzonden. Het kiezen van het antwoord dat naar de klant moet worden verzonden, omvat verschillende stappen:

  • het Facelet kiezen dat het antwoord zal genereren. Dit wordt de weergave V genoemd, de V van MVC. Deze keuze hangt doorgaans af van het resultaat van de uitvoering van de door de gebruiker gevraagde actie;
  • deze Facelet voorzien van de gegevens die hij nodig heeft om dit antwoord te genereren. Dit antwoord bevat namelijk meestal informatie die door de controller is berekend. Deze informatie vormt wat men het M-model van de weergave noemt, de M van MVC,

Stap 3 bestaat dus uit het kiezen van een weergave V en het opbouwen van het daarvoor benodigde model M.

4 - antwoord - de controller C vraagt de gekozen Facelet om zichzelf weer te geven. Deze gebruikt het door de controller C voorbereide model M om de dynamische delen van het antwoord te initialiseren dat zij naar de client moet sturen. De exacte vorm hiervan kan variëren: het kan een stream zijn van het type HTML, PDF, Excel, ...

In een project JSF:

  • is de C-controller de servlet [javax.faces.webapp.FacesServlet],
  • de weergaven V worden geïmplementeerd door pagina’s die gebruikmaken van de Facelets-technologie,
  • de modellen M en de gebeurtenishandlers worden geïmplementeerd door Java-klassen die vaak "backing beans" of kortweg Beans worden genoemd.

Laten we nog eens kijken naar de inhoud van het bestand [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>
  • regels 12-15: de tag <servlet-mapping> wordt gebruikt om een servlet te koppelen aan een URL die door de clientbrowser wordt opgevraagd. Hier wordt aangegeven dat URL-bestanden met de indeling [/faces/*] moeten worden verwerkt door de servlet met de naam [Faces Servlet]. Deze wordt gedefinieerd in de regels 7-10. Aangezien er geen andere <servlet-mapping>-tag in het bestand staat, betekent dit dat de servlet [Faces Servlet] alleen de URL-verzoeken van het type [/faces/*] zal verwerken. We hebben gezien dat de applicatiecontext [/mv-jsf2-01] heet. De URL-bestanden van klanten die door de servlet [Faces Servlet] worden verwerkt, zullen dus de vorm [http://machine:port/mv-jsf2-01/faces/*] hebben. De .html- en .jsp-pagina’s worden standaard door de servletcontainer zelf verwerkt, en niet door een specifieke servlet. De servletcontainer weet namelijk hoe deze moeten worden verwerkt,
  • regels 7-10: definiëren de servlet [Faces Servlet]. Aangezien alle geaccepteerde URL-verzoeken hiernaar worden doorgestuurd, is deze servlet de controller C van het model MVC,
  • regel 10: geeft aan dat de servlet bij het opstarten van de webserver in het geheugen moet worden geladen. Standaard wordt een servlet pas geladen bij ontvangst van het eerste verzoek dat ernaar wordt gestuurd,
  • regels 3-6: definiëren een parameter voor de servlet [Faces Servlet]. De parameter javax.faces.PROJECT_STAGE bepaalt in welke fase het uitgevoerde project zich bevindt. In de Development-fase geeft de servlet [Faces Servlet] foutmeldingen weer die nuttig zijn voor het debuggen. In de Production-fase worden deze meldingen niet meer weergegeven,
  • regels 17-19: duur van een sessie in minuten. Een client communiceert met de applicatie via een reeks verzoek/antwoord-cycli. Elke cyclus maakt gebruik van een eigen verbinding TCP-IP, die bij elke nieuwe cyclus opnieuw wordt aangemaakt. Als een klant C dus twee verzoeken doet, D1 en D2, kan de server S niet weten dat beide verzoeken van dezelfde klant C afkomstig zijn. De server S heeft geen toegang tot het geheugen van de klant. Dit wordt bepaald door het gebruikte protocol HTTP (HyperText Transport Protocol): de client communiceert met de server via een opeenvolging van cycli van clientverzoeken en serverantwoorden, waarbij telkens een nieuwe TCP-IP-verbinding wordt gebruikt. Dit wordt een stateloos protocol genoemd. Bij andere protocollen, zoals bijvoorbeeld FTP (File Transfer Protocol), gebruikt de client C dezelfde verbinding gedurende de hele communicatie met de server S. Een verbinding is dus gekoppeld aan een specifieke client. De server S weet dus altijd met wie hij te maken heeft. Om te kunnen herkennen dat een verzoek van een bepaalde client afkomstig is, kan de webserver gebruikmaken van de sessietechniek:
    • bij het eerste verzoek van een client stuurt server S hem het verwachte antwoord plus een token, een willekeurige reeks tekens die uniek is voor deze client;
    • bij elk volgend verzoek stuurt de klant C het ontvangen token terug naar server S, waardoor server S hem kan herkennen.

De applicatie heeft nu de mogelijkheid om de server te vragen informatie op te slaan die aan een bepaalde klant is gekoppeld. Dit wordt een klantsessie genoemd. Regel 18 geeft aan dat de levensduur van een sessie 30 minuten bedraagt. Dit betekent dat als een klant C gedurende 30 minuten geen nieuw verzoek indient, zijn sessie wordt beëindigd en de informatie die deze bevatte, verloren gaat. Bij zijn volgende verzoek zal alles verlopen alsof hij een nieuwe klant is en zal er een nieuwe sessie worden gestart,

  • regels 21-23: de lijst met pagina’s die moeten worden weergegeven wanneer de gebruiker de context opvraagt zonder een specifieke pagina te vermelden, bijvoorbeeld hier [http://machine:port/mv-jsf2-01]. In dit geval controleert de webserver (niet de servlet) of de applicatie een <welcome-file-list>-tag heeft gedefinieerd. Zo ja, dan wordt de eerste pagina uit de lijst weergegeven. Als die niet bestaat, de tweede pagina, enzovoort, totdat er een bestaande pagina wordt gevonden. Wanneer de client hier URL [http://machine:port/mv-jsf2-01] opvraagt, wordt URL [http://machine:port/mv-jsf2-01/index.xhtml] weergegeven.

2.3.5. Het project uitvoeren

Wanneer het nieuwe project wordt uitgevoerd, is het resultaat in de browser als volgt:

  • in [1] werd de context opgevraagd zonder specificatie van het document,
  • in [2] wordt, zoals uitgelegd, de startpagina (welcome-file) [index.xhtml] weergegeven.

Misschien is men nieuwsgierig naar de ontvangen broncode [3]:

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>

We hebben HTML ontvangen. Alle <h:xx>-tags uit index.xhtml zijn vertaald naar hun overeenkomstige tags in HTML.

2.3.6. De lokale Maven-repository

We hebben gezegd dat Maven de benodigde afhankelijkheden voor het project downloadt en deze lokaal opslaat. We kunnen deze lokale repository verkennen:

  • in [1], kies je de optie [Window / Other / Maven Repository Browser],
  • in [2], er wordt een tabblad [Maven Repositories] geopend,
  • in [3] bevinden zich twee takken: één voor de lokale repository en één voor de centrale repository. Deze laatste is gigantisch. Om de inhoud ervan te bekijken, moet de index [4] worden bijgewerkt. Deze update duurt enkele tientallen minuten.
  • in [5], de bibliotheken van de lokale repository,
  • naar [6]; daarin bevindt zich een branch [istia.st] die overeenkomt met de [groupId] van ons project,
  • Met [7] krijg je toegang tot de eigenschappen van de lokale repository,
  • in [8] vind je het pad naar de lokale repository. Het is handig om dit te weten, omdat Maven soms (zelden) niet meer de nieuwste versie van het project gebruikt. Je brengt wijzigingen aan en merkt dat deze niet worden meegenomen. Je kunt dan handmatig de tak in de lokale repository verwijderen die overeenkomt met onze [groupId]. Dit dwingt Maven om de tak opnieuw aan te maken op basis van de laatste versie van het project.

2.3.7. Een artefact zoeken met Maven

Laten we nu leren hoe je een artefact kunt zoeken met Maven. Laten we uitgaan van de lijst met huidige afhankelijkheden van het bestand [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>

De regels 13-40 definiëren afhankelijkheden en de regels 45-58 de repositories waar deze te vinden zijn, naast de centrale repository die altijd wordt gebruikt. We gaan de afhankelijkheden aanpassen om de bibliotheken in hun meest recente versie te gebruiken.

Allereerst verwijderen we de huidige afhankelijkheden [1]. Het bestand [pom.xml] wordt vervolgens aangepast:


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

Regels 5-12: de verwijderde afhankelijkheden komen niet meer voor in [pom.xml]. Laten we ze nu opzoeken in de Maven-repositories.

  • in [1] wordt een afhankelijkheid aan het project toegevoegd,
  • in [2] moeten we informatie opgeven over het gezochte artefact (groupId, artifactId, versie, packaging (Type) en scope). We beginnen met het specificeren van [groupId] [3],
  • in [4] typen we [espace] om de lijst met mogelijke artefacten weer te geven. Hier zijn dat [jsf-api] en [jsf-impl]. We kiezen [jsf-api],
  • en bij [5] kiezen we op dezelfde manier de meest recente versie. Het type packaging is jar.

We gaan op deze manier te werk voor alle artefacten:

In [6] verschijnen de toegevoegde afhankelijkheden in het project. Het bestand [pom.xml] weerspiegelt deze wijzigingen:


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

Stel nu dat we het [groupId] van het gewenste artefact niet kennen. We willen bijvoorbeeld Hibernate gebruiken als ORM (Object Relational Mapper) en dat is alles wat we weten. Dan kunnen we naar de website [http://mvnrepository.com/] gaan:

In [1] kun je trefwoorden invoeren. Laten we hibernate invoeren en de zoekopdracht starten.

  • in [2] kiezen we de [groupId], org.hibernate en de [artifactId], hibernate-core,
  • in [3], kiezen we de versie 4.1.2-Final,
  • in [4] krijgen we de Maven-code die we in het bestand [pom.xml] moeten plakken. Dat doen we.

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

We slaan het bestand [pom.xml] op. Maven begint vervolgens met het downloaden van de nieuwe afhankelijkheden. Het project ontwikkelt zich als volgt:

  • in [5], de afhankelijkheid [hibernate-core-4.1.2-Final]. In de repository waar het is gevonden, wordt dit [artifactId] ook beschreven door een bestand [pom.xml]. Dit bestand is gelezen en Maven heeft ontdekt dat het [artifactId] afhankelijkheden had. Deze worden ook gedownload. Dit gebeurt voor elk gedownload [artifactId]. Uiteindelijk vinden we in [6] afhankelijkheden die we niet rechtstreeks hadden aangevraagd. Deze worden aangegeven met een ander pictogram dan dat van het hoofdbestand [artifactId].

In dit document gebruiken we Maven voornamelijk vanwege deze eigenschap. Hierdoor hoeven we niet alle afhankelijkheden te kennen van een bibliotheek die we willen gebruiken. We laten Maven deze beheren. Bovendien zijn we er, door een [pom.xml]-bestand tussen ontwikkelaars te delen, zeker van dat elke ontwikkelaar inderdaad dezelfde bibliotheken gebruikt.

In de volgende voorbeelden volstaan we met het vermelden van het gebruikte [pom.xml]-bestand. De lezer hoeft dit alleen maar te gebruiken om in dezelfde omstandigheden te komen als in het document. Bovendien worden Maven-projecten ondersteund door de belangrijkste Java-omgevingen (Eclipse, NetBeans, IntelliJ, JDeveloper). De lezer kan dus zijn favoriete omgeving gebruiken om de voorbeelden te testen.

2.4. Voorbeeld mv-jsf2-02: gebeurtenisbeheer – internationalisering – navigatie tussen pagina’s

2.4.1. De applicatie

De applicatie ziet er als volgt uit:

  • in [1], de startpagina van de applicatie,
  • in [2], twee links om de taal van de pagina's van de applicatie te wijzigen,
  • naar [3], een navigatielink naar een andere pagina,
  • wanneer je op [3] klikt, wordt de pagina [4] weergegeven,
  • met de link [5] keert u terug naar de startpagina.
  • op de startpagina [1] kun je met de links [2] van taal wisselen,
  • op [3], de startpagina in het Engels.

2.4.2. Het NetBeans-project

Er wordt een nieuw webproject aangemaakt zoals uitgelegd in paragraaf 2.3.1. Dit krijgt de naam mv-jsf2-02:

  • in [1], het gegenereerde project,
  • in [2] hebben we het pakket [istia.st.mvjsf202] en het bestand [index.jsp] verwijderd,
  • In [3] zijn Maven-afhankelijkheden toegevoegd via het volgende bestand [pom.xml]:

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

De toegevoegde afhankelijkheden zijn die van het framework JSF. Kopieer de bovenstaande regels gewoon naar het bestand [pom.xml] ter vervanging van de oude afhankelijkheden.

  • in [4, 5]: maak een map [src / main / resources] aan in het tabblad [Files],
  • in [6], in het tabblad [Projects], waardoor de tak [Other Sources] is aangemaakt.

We hebben nu een project JSF. Hierin zullen we verschillende soorten bestanden aanmaken:

  • webpagina’s in het formaat XHTML,
  • Java-klassen,
  • berichtbestanden,
  • het configuratiebestand van het project JSF.

Laten we eens kijken hoe we elk type bestand kunnen aanmaken:

  • in [1] maken we een pagina JSF
  • in [2] maken we een pagina [index.xhtml] in het formaat [Facelets] [3],
  • in [4] zijn twee bestanden aangemaakt: [index.xhtml] en [WEB-INF / web.xml].

Het bestand [web.xml] configureert de toepassing JSF. Dit is het volgende:


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

We hebben dit bestand al besproken in paragraaf 2.3.4. Laten we de belangrijkste eigenschappen nog eens op een rijtje zetten:

  • alle URL-bestanden van het type faces/* worden verwerkt door de servlet [javax.faces.webapp.FacesServlet],
  • de pagina [index.xhtml] is de startpagina van de applicatie.

Het aangemaakte bestand [index.xhtml] ziet er als volgt uit:


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

We zijn dit bestand al tegengekomen in paragraaf 2.3.4.

Laten we nu een Java-klasse aanmaken:

  • in [1] maken we een Java-klasse aan in de tak [Source Packages],
  • in [2] geven we de klasse een naam en plaatsen we deze in het pakket [3],
  • in [4] verschijnt de aangemaakte klasse in het project.

De code van de aangemaakte klasse is een klasseskelet:


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package istia.st;

/**
 *
 * @author Serge Tahé
 */
public class Form {
  
}

Laten we ten slotte een berichtenbestand aanmaken:

  • in [1], aanmaken van een bestand [Properties],
  • in [2] geven we de bestandsnaam op en in [3] de map,
  • in [4] is het bestand [messages.properties] aangemaakt.

Soms is het nodig om het bestand [WEB-INF/faces-config.xml] aan te maken om het project JSF te configureren. Dit bestand was verplicht bij JSF 1. Het is optioneel bij JSF 2. Het is echter wel nodig als de website JSF geïnternationaliseerd is. Dit zal later het geval zijn. Daarom laten we nu zien hoe je dit configuratiebestand kunt aanmaken.

  • in [1] maken we het configuratiebestand JSF aan,
  • in [2] geven we de naam op en in [3] de map,
  • in [4], het aangemaakte bestand.

Het aangemaakte bestand [faces-config.xml] ziet er als volgt uit:


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

De root-tag is <faces-config>. De inhoud van deze tag is leeg. We zullen deze later invullen.

We hebben nu alle elementen om een project JSF aan te maken. In de volgende voorbeelden presenteren we het volledige project JSF en lichten we vervolgens de elementen één voor één toe. We presenteren nu een project om de begrippen uit te leggen:

  • het beheer van gebeurtenissen in een formulier,
  • de internationalisering van de pagina’s van een website JSF,
  • van navigatie tussen pagina's.

Het project [mv-jsf2-02] ziet er als volgt uit. De lezer kan het vinden op de voorbeeldwebsite (zie paragraaf 1.2).

  • in [1], de configuratiebestanden van het project JSF,
  • in [2], de pagina's JSF van het project,
  • in [3], de enige Java-klasse,
  • in [4], de berichtbestanden.

2.4.3. De pagina [index.xhtml]

Het bestand [index.xhtml] [1] stuurt de pagina [2] naar de browser van de klant:

De code die deze pagina genereert, is als volgt:


<?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>
  • regels 7-9: de naamruimten / tagbibliotheken die door de pagina worden gebruikt. De tags met het voorvoegsel h zijn HTML-tags, terwijl de tags met het voorvoegsel f eigen zijn aan JSF,
  • regel 10: de tag <f:view> dient om de code af te bakenen die door de JSF-engine moet worden verwerkt, namelijk de code waarin de tags <f:xx> voorkomen. Met het attribuut `locale` kan een weergavetaal voor de pagina worden opgegeven. Hier gebruiken we er twee: Engels en Frans. De waarde van het attribuut `local` wordt uitgedrukt in de vorm van een EL-uitdrukking (Expression Language) #{uitdrukking}. De vorm van de uitdrukking kan variëren. Meestal zullen we deze weergeven in de vorm `bean['clé']` of `bean.champ`. In onze voorbeelden is `bean` ofwel een Java-klasse ofwel een berichtenbestand. Met JSF 1 moesten deze beans worden gedeclareerd in het bestand [faces-config.xml]. Met JSF 2 is dit niet langer verplicht voor Java-klassen. Men kan nu annotaties gebruiken die van een Java-klasse een bean maken die bekend is bij JSF 2. Het berichtenbestand moet worden gedeclareerd in het configuratiebestand [faces-config.xml].

2.4.4. De bean [changeLocale]

In de uitdrukking EL #{changeLocale.locale}:

  • changeLocale is de naam van een bean, in dit geval de Java-klasse ChangeLocale,
  • locale is een veld van de klasse ChangeLocale. De uitdrukking wordt geëvalueerd door [ChangeLocale].getLocale(). In het algemeen wordt de uitdrukking #{bean.champ} geëvalueerd als [Bean].getChamp(), waarbij [Bean] een instantie is van de Java-klasse waaraan de naam bean en getChamp is toegekend, de getter die is gekoppeld aan het veld champ van de bean.

De klasse ChangeLocale is als volgt:


package utils;

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

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // de taal van de pagina’s
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  ...
  public String getLocale() {
    return locale;
  }
  
}
  • regel 11: het veld locale,
  • regel 17: de bijbehorende getter,
  • regel 7: de annotatie ManagedBean maakt van de Java-klasse ChangeLocale een bean die door JSF wordt herkend. Een bean wordt geïdentificeerd door een naam. Deze kan worden vastgelegd via het attribuut name van de annotatie: @ManagedBean(name= "xx "). Als het attribuut name ontbreekt, wordt de naam van de klasse gebruikt, waarbij het eerste teken in een kleine letter wordt omgezet. De naam van de bean ChangeLocale is dus changeLocale. Let erop dat de annotatie ManagedBean tot het pakket javax.faces.bean.ManagedBean behoort en niet tot het pakket javax.annotations.ManagedBean.
  • regel 8: de annotatie SessionScoped bepaalt het bereik van de bean. Er zijn er meerdere. We zullen doorgaans de volgende drie gebruiken:
    • RequestScoped: de levensduur van de bean is gelijk aan die van de cyclus van browserverzoek en serverantwoord. Als deze bean opnieuw nodig is om een nieuw verzoek van dezelfde of een andere browser te verwerken, wordt hij opnieuw geïnstantieerd,
    • SessionScoped: de levensduur van de bean is gelijk aan die van de sessie van een bepaalde client. De bean wordt in eerste instantie aangemaakt voor een van de verzoeken van deze client. Vervolgens blijft hij in het geheugen aanwezig binnen de sessie van deze client. Zo’n bean slaat doorgaans gegevens op die specifiek zijn voor een bepaalde client. Hij wordt vernietigd wanneer de sessie van de client wordt beëindigd,
    • ApplicationScoped: de levensduur van de bean is gelijk aan die van de applicatie zelf. Een bean met deze levensduur wordt meestal gedeeld door alle klanten van de applicatie. Hij wordt doorgaans geïnitialiseerd bij het opstarten van de applicatie.

Deze annotaties komen voor in twee pakketten: javax.enterprise.context.SessionScoped (JSF 2) en javax.faces.bean.SessionScoped (JSF 1). Hier gebruiken we het pakket JSF 2. Hierdoor moeten we het bestand [WEB-INF / beans.xml] aanmaken:

  

Dit bestand wordt automatisch door NetBeans gegenereerd wanneer het pakket [javax.enterprise.context.SessionScoped] wordt geïmporteerd. De inhoud ervan is als volgt:


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

Afgezien van de root-tag <beans> is het bestand leeg. Dat is voldoende. Alleen de aanwezigheid ervan is nodig.

Tot slot moet worden opgemerkt dat de klasse [ChangeLocale] de interface [Serializable] implementeert. Dit is verplicht voor beans met het bereik Session, die de webserver mogelijk in bestanden moet serialiseren. We zullen later terugkomen op de bean [ChangeLocale].

2.4.5. Het berichtenbestand

Laten we teruggaan naar het bestand [index.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">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
    ...
    </body>
  </f:view>
</html>
  • regel 8: de tag <h:outputText> geeft de waarde weer van een uitdrukking EL #{msg['welcome.titre']} in de vorm #{bean['champ']}. bean is ofwel de naam van een Java-klasse, ofwel die van een berichtenbestand. In dit geval is het de naam van een berichtenbestand. Dit bestand moet worden gedeclareerd in het configuratiebestand [faces-config.xml]. De bean msg wordt als volgt gedeclareerd:

<?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>
  • regels 11-18: de tag <application> dient om de applicatie JSF te configureren,
  • regels 12-17: de tag <resource-bundle> wordt gebruikt om bronnen voor de applicatie te definiëren, in dit geval een berichtenbestand,
  • regels 13-15: de tag <base-name> definieert de naam van het berichtenbestand,
  • regel 14: het bestand krijgt de naam messages[_CodeLangue][_CodePays].properties. De tag <base-name> definieert alleen het eerste deel van de naam. De rest wordt impliciet bepaald. Er kunnen meerdere berichtenbestanden bestaan, één per taal:
  • in [1] zien we vier berichtenbestanden die overeenkomen met de basisnaam ‘messages’ die is gedefinieerd in [faces-config.xml],
    • messages_fr.properties: bevat de berichten in het Frans (code fr);
    • messages_en.properties: bevat de berichten in het Engels (code en);
    • messages_es_ES.properties: bevat de berichten in het Spaans (code es) van Spanje (code ES). Er bestaan andere varianten van het Spaans, bijvoorbeeld die van Bolivia (es_BO);
    • messages.properties: wordt door de server gebruikt wanneer er voor de taal van de computer waarop deze draait geen bijbehorend berichtenbestand is. Dit zou bijvoorbeeld het geval zijn als de applicatie op een computer in Duitsland zou draaien, waar de standaardtaal Duits (de) is. Aangezien er geen bestand [messages_de.properties] bestaat, zou de applicatie het bestand [messages.properties] gebruiken,
  • in [2]: de taalcodes zijn vastgelegd in een internationale standaard,
  • in [3]: hetzelfde geldt voor de landcodes.

De bestandsnaam van de berichten wordt gedefinieerd op regel 14. Deze wordt gezocht in het bestand Classpath van het project. Als het bestand zich in een pakket bevindt, moet dit pakket op regel 14 worden gedefinieerd, bijvoorbeeld ressources.messages, als het bestand [messages.properties] zich bevindt in de map [ressources] van Classpath. Aangezien de naam op regel 14 geen pakket bevat, moet het bestand [messages.properties] in de hoofdmap van de map [src / main / resources] worden geplaatst:

In [1], op het tabblad [Projects] van het NetBeans-project, wordt het bestand [messages.properties] weergegeven als een lijst met de verschillende gedefinieerde berichtversies. De versies worden aangeduid met een reeks van één tot drie codes [codeLangue_codePays_codeVariante]. In [1] is alleen de code [codeLangue] gebruikt: en voor het Engels, fr voor het Frans. Elke versie is ondergebracht in een apart bestand in het bestandssysteem.

In ons voorbeeld bevat het Franstalige berichtbestand [messages_fr.properties] de volgende elementen:


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

Het bestand [messages_en.properties] ziet er als volgt uit:


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

Het bestand [messages.properties] is identiek aan het bestand [messages_en.properties]. Uiteindelijk heeft de browser van de klant de keuze tussen pagina’s in het Frans en pagina’s in het Engels.

Laten we teruggaan naar het bestand [faces-config.xml], waarin het berichtenbestand wordt gedefinieerd:


...

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

Regel 8 geeft aan dat een regel uit het berichtenbestand wordt aangeduid met de identificatiecode msg in de pagina's JSF. Deze identificatiecode wordt gebruikt in het eerder besproken bestand [index.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">
  <f:view locale="#{changeLocale.locale}">
    <head>
      <title><h:outputText value="#{msg['welcome.titre']}" /></title>
    </head>
    <body>
      ...
    </body>
  </f:view>
</html>

De tag <h:outputText> op regel 8 geeft de waarde van het bericht weer (aanwezigheid van de identificatiecode msg) met sleutel welcome.titre. Dit bericht wordt gezocht en gevonden in het bestand [messages.properties] van de momenteel actieve taal. Bijvoorbeeld, voor het Frans:


welcome.titre=Tutoriel JSF (JavaServer Faces)

Een bericht heeft de vorm sleutel=waarde. Regel 8 van het bestand [index.xhtml] ziet er na evaluatie van de uitdrukking #{msg['welcome.titre']} als volgt uit:


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

Met dit mechanisme van berichtbestanden kan de taal van de pagina’s van een JSF-project eenvoudig worden gewijzigd. We spreken van internationalisering van het project, of vaker van de afkorting i18n, omdat het woord internationalisering begint met een i en eindigt op een n, en er 18 letters tussen de i en de n zitten.

2.4.6. Het formulier

Laten we verdergaan met het verkennen van de inhoud van het bestand [index.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">
  <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>
  • regels 11-18: de tag <h:form> introduceert een formulier. Een formulier bestaat doorgaans uit:
    • tags voor invoervelden (tekst, keuzerondjes, selectievakjes, keuzelijsten, ...);
    • tags voor formuliervalidatie (knoppen, links). Via een knop of een link verstuurt de gebruiker zijn invoer naar de server, die deze vervolgens verwerkt;

Elke JSF-tag kan worden geïdentificeerd door een id-attribuut. Meestal kan men dit achterwege laten en dat is ook gebeurd bij de meeste JSF-tags die hier worden gebruikt. Toch is dit attribuut in bepaalde gevallen nuttig. Regel 17: het formulier wordt geïdentificeerd door de id ‘formulier’. In dit voorbeeld wordt de id van het formulier niet gebruikt en had deze dus weggelaten kunnen worden.

  • regels 18-21: de tag <h:panelGrid> definieert hier een HTML-tabel met twee kolommen. Deze tag genereert de tag <table>,
  • het formulier bevat drie links die de verwerking ervan activeren, in de regels 19, 20 en 23. De tag <h:commandLink> heeft ten minste twee attributen:
    • value: de tekst van de link;
    • action: ofwel een C-tekenreeks, ofwel de verwijzing naar een methode die na uitvoering de C-tekenreeks retourneert. Deze C-tekenreeks kan:
      • ofwel de naam van een pagina JSF van het project,
      • ofwel een naam die is gedefinieerd in de navigatieregels van het bestand [faces-config.xml] en gekoppeld is aan een pagina JSF van het project;

In beide gevallen wordt de pagina JSF weergegeven, zodra de actie die is gedefinieerd door het attribuut action is uitgevoerd.

Laten we eens kijken naar de werking van de verwerking van formulieren aan de hand van de link in regel 13:


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

Allereerst wordt het berichtenbestand gebruikt om de uitdrukking #{msg['welcome.langue1']} te vervangen door de bijbehorende waarde. Na evaluatie wordt de tag:


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

De vertaling HTML van deze tag JSF zal als volgt zijn:

<a href="<a href="view-source:http://localhost:8080/mv-jsf2-02/faces/page1.xhtml#">#</a>" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),{'formulaire:j_idt8':'formulaire:j_idt8'},'');return false">Français</a>

wat het volgende visuele resultaat oplevert:

Let op het onclick-attribuut van de tag HTML <a>. Wanneer de gebruiker op de link [Français] klikt, wordt er JavaScript-code uitgevoerd. Deze code is ingebed in de pagina die de browser heeft ontvangen en wordt door de browser zelf uitgevoerd. JavaScript-code wordt op grote schaal gebruikt in de technologieën JSF en AJAX (Asynchronous JavaScript and XML). Het doel ervan is doorgaans om de gebruiksvriendelijkheid en de reactiesnelheid van webapplicaties te verbeteren. Meestal wordt deze code automatisch gegenereerd door softwaretools en is het dan niet nodig om deze te begrijpen. Maar soms kan een ontwikkelaar genoodzaakt zijn om JavaScript-code toe te voegen aan zijn JSF-pagina’s. Kennis van JavaScript is dan wel noodzakelijk.

Het is hier niet nodig om de gegenereerde JavaScript-code voor de tag JSF <h:commandLink> te begrijpen. Er zijn echter twee punten die de aandacht verdienen:

  • de JavaScript-code gebruikt de formulier-ID die we aan de tag JSF <h:form> hebben toegekend,
  • JSF genereert automatische ID’s voor alle tags waarvoor het id-attribuut niet is gedefinieerd. Hier zien we een voorbeeld: j_idt8. Door tags een duidelijke identificatiecode te geven, wordt de gegenereerde JavaScript-code beter begrijpelijk mocht dat nodig zijn. Dit is met name het geval wanneer de ontwikkelaar zelf JavaScript-code moet toevoegen die de componenten van de pagina bewerkt. Hij moet dan de id-identificatiecodes van zijn componenten kennen.

Wat gebeurt er als de gebruiker op de link [Français] op de bovenstaande pagina klikt? Laten we eens kijken naar de architectuur van een applicatie JSF:

De controller [Faces Servlet] ontvangt het verzoek van de clientbrowser in de volgende vorm: 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 
  • regels 1-2: de browser vraagt om de URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. Dit is altijd het geval: de invoer in een formulier JSF, dat aanvankelijk werd verkregen met de URL URLFormulaire, wordt naar diezelfde URL verzonden. De browser heeft twee manieren om de ingevoerde waarden te verzenden: GET en POST. Met de methode GET worden de ingevoerde waarden door de browser verzonden in het aangevraagde URL-formulier. In het bovenstaande voorbeeld had de browser de volgende eerste regel kunnen verzenden:

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

Met de hier gebruikte methode POST verstuurt de browser de ingevoerde waarden via regel 6 naar de server.

  • regel 3: geeft de coderingsvorm van de formulierwaarden aan,
  • regel 4: geeft de grootte in bytes van regel 6 aan,
  • regel 5: lege regel die het einde van de headers HTTP en het begin van de 126 bytes aan formulierwaarden aangeeft,
  • regel 6: de formulierwaarden in de vorm element1=waarde1&element2=waarde2& ..., de coderingsvorm zoals gedefinieerd in regel 3. In deze coderingsvorm worden bepaalde tekens vervangen door hun hexadecimale waarde. Dit is het geval in het laatste element:

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

waarbij %3A het teken : vertegenwoordigt. Het is dus de tekenreeks formulier:j_idt8=formulier:j_idt8 die naar de server wordt verzonden. Misschien herinner je je nog dat we de identificatiecode j_idt8 al zijn tegengekomen toen we de code HTML bekeken die werd gegenereerd voor de tag


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

Deze was automatisch gegenereerd door JSF. Wat hier van belang is, is dat de aanwezigheid van deze identificatiecode in de reeks waarden die door de clientbrowser wordt verzonden, JSF in staat stelt te weten dat er op de link [Français] is geklikt. Vervolgens gebruikt het het bovenstaande action-attribuut om te bepalen hoe de ontvangen reeks moet worden verwerkt. Het attribuut action="#{changeLocale.setFrenchLocale}" geeft aan JSF door dat het verzoek van de client moet worden verwerkt door de methode [setFrenchLocale] van een object met de naam changeLocale. We herinneren ons dat deze bean is gedefinieerd door middel van annotaties in de Java-klasse [ChangeLocale]:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

De naam van een bean wordt bepaald door het attribuut name van de annotatie @ManagedBean. Als dit attribuut ontbreekt, wordt de naam van de klasse gebruikt als bean-naam, waarbij het eerste teken in kleine letters wordt weergegeven.

Laten we teruggaan naar de verzoek van de browser:

en naar de tag <h:commandLink> die de link [Français] heeft gegenereerd waarop is geklikt:


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

De controller stuurt het verzoek van de browser door naar de gebeurtenishandler die is gedefinieerd door het action-attribuut van de tag <h:commandLink>. De gebeurtenishandler M waarnaar wordt verwezen door het action-attribuut van een commando <h:commandLink> moet de volgende handtekening hebben:

public String M();
  • hij ontvangt geen parameters. We zullen zien dat hij niettemin toegang kan hebben tot het verzoek van de client,
  • en moet een resultaat C van het type String retourneren. Deze tekenreeks C kan zijn:
    • ofwel de naam van een pagina JSF uit het project;
    • ofwel een naam die is gedefinieerd in de navigatieregels van het bestand [faces-config.xml] en gekoppeld is aan een pagina JSF van het project;
    • ofwel een null-pointer, als de clientbrowser niet van pagina moet wisselen,

In de bovenstaande architectuur JSF zal de controller [Faces Servlet] de door de gebeurtenisverwerker geretourneerde tekenreeks C en eventueel zijn configuratiebestand [faces-config.xml] gebruiken om te bepalen welke pagina JSF, hij als antwoord naar de client [4] moet verzenden.

In de tag


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

is de methode [changeLocale.setFrenchLocale] van de gebeurtenishandler voor het klikken op de link [Français], waarbij changeLocale een instantie is van de reeds besproken klasse [utils.ChangeLocale] :


package utils;

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

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // de taalinstelling van de pagina's
  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;
  }
}

De methode setFrenchLocale heeft inderdaad de handtekening van de gebeurtenishandlers. Laten we niet vergeten dat de gebeurtenishandler het verzoek van de client moet verwerken. Aangezien hij geen parameters ontvangt, hoe kan hij dan toegang krijgen tot dit verzoek? Er zijn verschillende manieren om dit te doen:

  • De bean B, die de gebeurtenishandler van pagina JSF P bevat, is vaak ook degene die het model M van deze pagina bevat. Dit betekent dat de bean B velden bevat die worden geïnitialiseerd met de waarden die op pagina P zijn ingevoerd. Dit gebeurt door de controller [Faces Servlet] voordat de gebeurtenishandler van de bean B wordt aangeroepen. Deze handler heeft dus via de velden van de bean B waartoe hij behoort toegang tot de waarden die de klant in het formulier heeft ingevoerd en kan deze verwerken.
  • De statische methode [FacesContext.getCurrentInstance()] van het type [FacesContext] geeft toegang tot de uitvoeringscontext van de huidige aanvraag JSF, die een object is van het type [FacesContext]. Met de aldus verkregen uitvoeringscontext van de query is het mogelijk om toegang te krijgen tot de parameters die door de clientbrowser naar de server zijn verzonden met de volgende methode:
Map FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()

Als de door de clientbrowser verzonden parameters (POST) als volgt zijn:

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

dan retourneert de methode getRequestParameterMap() het volgende woordenboek:

sleutel
waarde
formulier
formulier
javax.faces.ViewState
...
formulier:j_id_id21
formulier:j_id_id21

In de tag


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

wat wordt er verwacht van de gebeurtenisverwerker locale.setFrenchLocale? We willen dat deze de taal vastlegt die door de applicatie wordt gebruikt. In Java-jargon noemt men dit het „lokaliseren“ van de applicatie. Deze lokalisatie wordt gebruikt door de tag <f:view> van de pagina JSF [index.xhtml]:


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

Om de pagina in het Frans weer te geven, volstaat het dat het attribuut locale de waarde fr heeft. Om de pagina in het Engels weer te geven, moet het de waarde en krijgen. De waarde van het attribuut locale wordt verkregen via de uitdrukking *[ChangeLocale].getLocale()*. Deze uitdrukking geeft de waarde van het veld locale van de klasse [ChangeLocale] weer. Hieruit kunnen we de code afleiden van de methode [ChangeLocale].setFrenchLocale(), die de pagina’s in het Frans moet weergeven:


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

We hebben uitgelegd dat een gebeurtenisverwerker een C-tekenreeks moet retourneren die door [Faces Servlet] wordt gebruikt om de pagina JSF te vinden die als antwoord naar de clientbrowser moet worden verzonden. Als de terug te sturen pagina dezelfde is als de pagina die momenteel wordt verwerkt, kan de gebeurtenishandler volstaan met het terugsturen van de waarde null. Dit gebeurt hier in regel 3: we willen dezelfde pagina [index.xhtml] terugsturen, maar in een andere taal.

Laten we terugkeren naar de architectuur voor de verwerking van het verzoek:

De gebeurtenisverwerker changeLocale.setFrenchLocale is uitgevoerd en heeft de waarde null teruggestuurd naar de controller [Faces Servlet]. Deze zal dus de pagina [index.xhtml] opnieuw weergeven. Laten we deze nog eens bekijken:


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

Telkens wanneer een waarde van het type #{msg['...']} wordt geëvalueerd, wordt een van de berichtbestanden [messages.properties] gebruikt. Het bestand dat wordt gebruikt, is het bestand dat overeenkomt met de „lokalisatie” van de pagina (regel 6). Aangezien de gebeurtenisverwerker changeLocale.setFrenchLocale deze lokalisatie definieert als fr, wordt het bestand [messages_fr.properties] gebruikt. Als u op de link [Anglais] (regel 14) klikt, wordt de lokalisatie gewijzigd in en (zie methode changeLocale.setEnglishLocale). Dan wordt het bestand [messages_en.properties] gebruikt en verschijnt de pagina in het Engels:

Telkens wanneer de pagina [index.xhtml] wordt weergegeven, wordt de tag <f:view> uitgevoerd:


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

en dus wordt de methode [ChangeLocale].getLocale() opnieuw uitgevoerd. Aangezien we onze bean het bereik ‘Session’ hebben gegeven:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

blijft de lokalisatie die bij een verzoek is uitgevoerd, behouden voor de volgende verzoeken.

Er blijft nog één laatste onderdeel van de pagina [index.xhtml] over om te bekijken:


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

De tag <h:commandLink> op regel 17 heeft een attribuut action dat gelijk is aan een tekenreeks. In dit geval wordt er geen gebeurtenishandler aangeroepen om de pagina te verwerken. We gaan direct door naar de pagina [page1.xhtml]. Laten we eens kijken hoe de applicatie in dit gebruiksscenario werkt:

De gebruiker klikt op de link [Page 1]. Het formulier wordt verzonden naar de controller [Faces Servlet]. Deze herkent in het verzoek dat hij ontvangt dat er op de link [Page 1] is geklikt. Hij onderzoekt de bijbehorende tag:


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

Er is geen gebeurtenishandler gekoppeld aan de link. De controller [Faces Servlet] gaat onmiddellijk door naar de bovenstaande stap [3] en geeft de pagina [page1.xhtml] weer:

2.4.7. De pagina JSF [page1.xhtml]

De pagina [page1.xhtml] stuurt de volgende stream naar de clientbrowser:

 

De code die deze pagina genereert, is als volgt:


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

Er staat op deze pagina niets dat niet al is uitgelegd. De lezer zal zelf de koppeling leggen tussen de code JSF en de pagina die naar de browser van de klant wordt verzonden. De link terug naar de startpagina:


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

zal de pagina [index.xhtml] weergeven.

2.4.8. Het project uitvoeren

Ons project is nu voltooid. We kunnen het compileren (Clean and Build):

  • door het project te bouwen wordt in het tabblad [Files] de map [target] aangemaakt. Daarin bevindt zich het archief [mv-jsf2-02-1.0-SNAPSHOT.war] van het project. Dit archief wordt op de server geïmplementeerd,
  • in [WEB-INF / classes] en [2] bevinden zich de gecompileerde klassen uit de map [Source Packages] van het project, evenals de bestanden die zich in de branch [Other Sources] bevonden, in dit geval de berichtbestanden,
  • in [WEB-INF / lib] [3] bevinden zich de bibliotheken van het project,
  • in de hoofdmap van [WEB-INF] en [4] bevinden zich de configuratiebestanden van het project,
  • in de hoofdmap van het archief [5] bevinden zich de pagina's JSF die zich in de tak [Web Pages] van het project bevonden,
  • zodra het project is gebouwd, kan het worden uitgevoerd: [6]. Het wordt uitgevoerd volgens de uitvoeringsconfiguratie [7],
  • de Tomcat-server wordt gestart als deze nog niet actief was ([8]),
  • het archief [mv-jsf2-02-1.0-SNAPSHOT.war] wordt op de server geladen. Dit wordt de implementatie van het project op de applicatieserver genoemd,
  • in [9] wordt gevraagd om tijdens de uitvoering een browser te starten. Deze zal de context van de applicatie opvragen [10], c.a.d. de URL [http://localhost:8080/mv-jsf2-02]. Volgens de regels van het bestand [web.xml] (zie pagina 44) wordt het bestand [faces/index.xhtml] aan de clientbrowser geleverd. Aangezien het bestand URL de vorm [/faces/*] heeft, wordt het verwerkt door de controller [Faces Servlet] (zie [web.xml] op pagina 44). Deze verwerkt de pagina en verstuurt de volgende HTML-stream:
 
  • De controller [Faces Servlet] verwerkt vervolgens de gebeurtenissen die vanaf deze pagina plaatsvinden.

2.4.9. Het configuratiebestand [faces-config.xml]

We hebben het volgende configuratiebestand [faces-config.xml] gebruikt:


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

Dit is het minimale configuratiebestand voor een geïnternationaliseerde JSF 2-toepassing. We hebben hier gebruikgemaakt van nieuwe mogelijkheden van JSF 2 ten opzichte van JSF 1:

  • het declareren van beans en hun bereik met de annotaties @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
  • tussen pagina’s navigeren met de namen van de pagina’s XHTML als navigatiesleutels, zonder het achtervoegsel xhtml.

Het kan zijn dat men deze mogelijkheden niet wil gebruiken en deze elementen van het project JSF in [faces-config.xml] en in JSF 1 wil declareren. In dat geval zou het bestand [faces-config.xml] er als volgt uit kunnen zien:


<?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">
<!-- applicatie -->
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
  
  <!-- beheerde 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>

   <!-- navigatie -->
  <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>
  • regels 20-24: declaratie van de bean changeLocale:
    • regel 21: naam van de bean;
    • regel 22: volledige naam van de klasse die aan de bean is gekoppeld;
    • regel 23: bereik van de bean. Mogelijke waarden zijn request, session, application,
  • regels 27-34: declaratie van een navigatieregel:
    • regel 28: de regel kan worden beschreven. Hier is dat niet gedaan;
    • regel 29: de pagina van waaruit wordt genavigeerd (startpunt);
    • regels 30-33: een navigatiegeval. Er kunnen er meerdere zijn;
    • regel 31: de navigatiesleutel;
    • regel 32: de pagina waarnaar wordt genavigeerd.

De navigatieregels kunnen op een meer visuele manier worden weergegeven. Wanneer het bestand [faces-config.xml] wordt bewerkt, kan men het tabblad [PageFlow] gebruiken:

 

Stel dat we het vorige bestand [faces-config.xml] gebruiken. Hoe zou onze toepassing er dan uitzien?

  • In de klasse [ChangeLocale] zouden de annotaties @ManagedBean en @SessionScoped verdwijnen, aangezien de bean nu in [faces-config] is gedeclareerd,
  • De navigatie van [index.xhtml] naar [page1.xhtml] via een link zou dan als volgt worden:

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

Aan het attribuut ‘action’ wordt de navigatiesleutel p1 toegewezen die is gedefinieerd in [faces-config],

  • de navigatie van [page1.xhtml] naar [index.xhtml] via een link zou dan als volgt worden:

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

Aan het attribuut ‘action’ wordt de navigatiesleutel ‘welcome’ toegewezen die is gedefinieerd in [faces-config],

  • de methoden setFrenchLocale en setEnglishLocale, die een navigatiesleutel moeten retourneren, hoeven niet te worden aangepast, omdat ze ‘null’ retourneerden om aan te geven dat men op dezelfde pagina bleef.

2.4.10. Conclusie

Laten we terugkeren naar het NetBeans-project dat we hebben geschreven:

Dit project heeft de volgende architectuur:

In elk project JSF vinden we de volgende elementen:

  • pagina's JSF [A] die door de controller [Faces Servlet] [3] naar de browsers van de klanten worden verzonden [4],
  • berichtbestanden [C] waarmee de taal van de pagina’s JSF kan worden gewijzigd,
  • Java-klassen [B] die gebeurtenissen verwerken die zich voordoen in de clientbrowser [2a, 2b] en/of die als sjablonen dienen voor de pagina's JSF en [3]. Meestal worden de lagen [métier] en [DAO] afzonderlijk ontwikkeld en getest. De laag [web] wordt vervolgens getest met een fictieve laag [métier]. Als de lagen [métier] en [DAO] beschikbaar zijn, wordt meestal met hun .jar-archieven gewerkt.
  • configuratiebestanden [D] om deze verschillende elementen aan elkaar te koppelen. Het bestand [web.xml] is beschreven op pagina 44 en zal zelden worden gewijzigd. Hetzelfde geldt voor [faces-config], waarvoor we altijd de vereenvoudigde versie zullen gebruiken.

2.5. Voorbeeld mv-jsf2-03: invoerformulier – componenten JSF

Vanaf nu zullen we de opbouw van het project niet meer laten zien. We presenteren kant-en-klare projecten en leggen uit hoe deze werken. De lezer kan alle voorbeelden downloaden via de website van dit document (zie paragraaf 1.2).

2.5.1. De applicatie

De applicatie heeft één enkele weergave:

De applicatie toont de belangrijkste componenten JSF die in een invoerformulier kunnen worden gebruikt:

  • de kolom [1] geeft de naam aan van de gebruikte tag JSF / HTML,
  • de kolom [2] toont een invoervoorbeeld voor elk van de aangetroffen tags,
  • de kolom [3] toont de waarden van de bean die als sjabloon voor de pagina dient,
  • de invoer in [2] wordt gevalideerd via de knop [4]. Deze validatie zorgt er alleen voor dat de model-bean van de pagina wordt bijgewerkt. Vervolgens wordt dezelfde pagina opnieuw weergegeven. Na validatie toont de kolom [3] dus de nieuwe waarden van de sjabloon-bean, waardoor de gebruiker kan controleren welk effect zijn invoer heeft op het sjabloon van de pagina.

2.5.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

  • in [1], de configuratiebestanden van het project JSF,
  • in [2], de enige pagina van het project: index.xhtml,
  • in [3], een stylesheet [styles.css] om het uiterlijk van de pagina [index.xhtml] te configureren
  • in [4], de Java-klassen van het project,
  • in [5], het bestand met de berichten van de applicatie in twee talen: Frans en Engels.

2.5.3. Het bestand [pom.xml]

We geven alleen de afhankelijkheden weer:


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

Dit zijn de afhankelijkheden die nodig zijn voor een project JSF. In de voorbeelden die volgen, wordt dit bestand alleen getoond wanneer het verandert.

2.5.4. Het bestand [web.xml]

Het bestand [web.xml] is zo geconfigureerd dat de pagina [index.xhtml] de startpagina van het project is:


<?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>
  • regel 30: de pagina [index.xhtml] is de startpagina,
  • regels 11-14: een instelling voor de servlet [Faces Servlet]. Deze zorgt ervoor dat opmerkingen in een facelet van het type:

        <!-- talen -->

worden genegeerd. Zonder deze parameter veroorzaken de opmerkingen onduidelijke problemen,

  • regels 3-6: een parameter voor de servlet [Faces Servlet], die verderop wordt uitgelegd.

2.5.5. Het bestand [faces-config.xml]

Het bestand [faces-config.xml] van de applicatie ziet er als volgt uit:


<?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>
  • regels 11-16: configureren het berichtenbestand van de applicatie.

2.5.6. Het berichtenbestand [messages.properties]

De berichtenbestanden (zie [5] in de schermafbeelding van het project) zijn als volgt:

[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

Deze berichten worden op de volgende plaatsen op de pagina weergegeven:

De Engelse versie van de berichten luidt als volgt:

[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. Het sjabloon [Form.java] van de pagina [index.xhtml]

In het bovenstaande project zal de klasse [Form.java] dienen als sjabloon of backing bean voor de pagina JSF [index.xhtml]. Laten we dit concept van een sjabloon illustreren met een voorbeeld uit de pagina [index.xhtml]:


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

Bij de eerste aanroep van de pagina [index.xhtml] genereert de bovenstaande code regel 2 van de invoertabel:

Regel 2 geeft het veld [1] weer, de regels 3-6: het veld [2], regel 7: het veld [3].

In de regels 5 en 7 wordt de uitdrukking EL gebruikt, waarbij de form-bean die is gedefinieerd in de klasse [Form.java] op de volgende manier wordt ingezet:


package forms;

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


@ManagedBean
@RequestScoped
public class Form {
  • regel 7 definieert een naamloze bean. Dit is dus de naam van de klasse die met een kleine letter begint: form,
  • de bean heeft een request-bereik. Dit betekent dat hij in een cyclus van clientverzoek en serverantwoord wordt geïnstantieerd wanneer het verzoek dit nodig heeft en wordt verwijderd wanneer het antwoord aan de client is teruggestuurd.

In de onderstaande code van de pagina [index.xhtml]:


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

worden in de regels 5 en 7 de waarde inputText van de bean form gebruikt. Om de verbanden tussen een pagina P en het bijbehorende model M te begrijpen, moeten we terugkeren naar de cyclus (verzoek van de client / antwoord van de server) die kenmerkend is voor een webapplicatie:

Er moet onderscheid worden gemaakt tussen het geval waarin pagina P als antwoord naar de browser wordt verzonden (stap 4), bijvoorbeeld bij het eerste verzoek om de pagina, en het geval waarin de gebruiker een gebeurtenis op pagina P heeft geactiveerd, die vervolgens wordt verwerkt door de controller [Faces Servlet] (stap 1).

We kunnen deze twee gevallen onderscheiden door ze vanuit het perspectief van de browser te bekijken:

  1. bij de eerste aanvraag van de pagina voert de browser een bewerking GET uit op de URL van de pagina,
  2. bij het verzenden van de op de pagina ingevoerde waarden voert de browser een bewerking POST uit op de URL van de pagina.

In beide gevallen wordt dezelfde URL opgevraagd. Afhankelijk van de aard van de aanvraag (GET of POST) door de browser, zal de verwerking van het verzoek verschillen.

[cas 1 – demande initiale de la page P]

De browser vraagt de URL van de pagina op met een GET. De controller [Faces Servlet] gaat direct door naar de stap [4] voor het weergeven van het antwoord en de pagina [index.xhtml] wordt naar de client verzonden. De controller JSF zal elke tag van de pagina vragen om weergegeven te worden. Laten we als voorbeeld regel 5 van de code van [index.xhtml] nemen:


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

De tag JSF <h:inputText value="waarde"/> genereert de tag HTML <input type="text" value="waarde"/>. De klasse die deze tag verwerkt, komt de uitdrukking #{form.inputText} tegen, die ze moet evalueren:

  • als de form-bean nog niet bestaat, wordt deze aangemaakt door de klasse forms.Form te instantiëren,
  • de uitdrukking #{form.inputText} wordt geëvalueerd door de methode form.getInputText() aan te roepen,
  • de tekst <input id="formulier:inputText" type="text" name="formulaire:inputText" value="tekst" /> wordt ingevoegd in de stroom HTML die naar de client wordt verzonden, ervan uitgaande dat de methode form.getInputText() de tekenreeks "tekst" heeft geretourneerd. JSF geeft bovendien een naam (name) aan de component HTML die in de stroom is geplaatst. Deze naam wordt samengesteld op basis van de id-identificatoren van de geanalyseerde component JSF en die van de bovenliggende componenten, in dit geval de tag <h:form id="formulaire"/>.

Houd er rekening mee dat als op een pagina P de uitdrukking #{M.champ} wordt gebruikt, waarbij M de model-bean van pagina P is, deze moet beschikken over de openbare methode getChamp(). Het type dat door deze methode wordt geretourneerd, moet kunnen worden geconverteerd naar het type String. Een mogelijk en veelvoorkomend model M is het volgende:

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

waarbij T een type is dat kan worden geconverteerd naar het type String, eventueel met behulp van de methode toString.

Nog steeds in het geval van de weergave van pagina P, de verwerking van de regel:


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

zal op dezelfde manier verlopen en de volgende HTML-stroom zal worden aangemaakt:

texte

Intern op de server wordt pagina P weergegeven als een boomstructuur van componenten, die een afspiegeling is van de boomstructuur van de tags van de pagina die naar de klant wordt verzonden. We noemen deze boomstructuur de weergave of de ‘ -status’ van de pagina. Deze status wordt opgeslagen. Dit kan op twee manieren gebeuren, afhankelijk van een configuratie in het bestand [web.xml] van de applicatie:


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

De regels 7-11 definiëren de controller [Faces Servlet]. Deze kan worden geconfigureerd via verschillende <context-param>-tags, waaronder die in de regels 3-6, die aangeven dat de status van een pagina op de client (de browser) moet worden opgeslagen. De andere mogelijke waarde, in regel 5, is ‘server’ om aan te geven dat de status op de server moet worden opgeslagen. Dit is de standaardwaarde.

Wanneer de status van een pagina op de client wordt opgeslagen, voegt de controller JSF aan elke HTML-pagina die hij verstuurt een verborgen veld toe, waarvan de waarde de huidige status van de pagina is. Dit verborgen veld heeft de volgende vorm:

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

De waarde ervan vertegenwoordigt in gecodeerde vorm de status van de naar de client verzonden pagina. Het is belangrijk om te begrijpen dat dit verborgen veld deel uitmaakt van het formulier van de pagina en dus deel zal uitmaken van de waarden die door de browser worden verzonden bij het verzenden van het formulier. Aan de hand van dit verborgen veld kan de controller JSF de weergave herstellen zoals deze naar de klant is verzonden.

Wanneer de status van een pagina op de server wordt opgeslagen, wordt de status van de naar de klant verzonden pagina opgeslagen in de sessie van de klant. Wanneer de browser van de klant de in het formulier ingevoerde waarden verstuurt, stuurt deze ook zijn sessietoken mee. Aan de hand hiervan zal de controller JSF de status van de naar de klant verzonden pagina terugvinden en herstellen.

De status van een pagina JSF kan enkele honderden bytes in beslag nemen om te worden gecodeerd. Aangezien deze status voor elke gebruiker van de applicatie wordt bijgehouden, kunnen er geheugenproblemen ontstaan als er een groot aantal gebruikers is. Om deze reden hebben we er hier voor gekozen om de status van de pagina op de client op te slaan (zie [web.xml], paragraaf 2.5.4, pagina 66).

[cas 2 – traitement de la page P]

We bevinden ons in de bovenstaande stap [1], waarbij de controller [Faces Servlet] een verzoek POST ontvangt van de clientbrowser waaraan hij eerder de pagina [index.xhtml] heeft verzonden. Er is sprake van de verwerking van een gebeurtenis op de pagina. Er zullen verschillende stappen plaatsvinden voordat de gebeurtenis in [2a] kan worden verwerkt. De verwerkingscyclus van een verzoek POST door de controller JSF is als volgt:

FEDCBA

Image

  • in [A] wordt, dankzij het verborgen veld javax.faces.ViewState, de weergave die aanvankelijk naar de clientbrowser werd verzonden, gereconstrueerd. Hier krijgen de componenten van de pagina weer de waarde die ze hadden in de verzonden pagina. Onze component inputText krijgt zijn waarde „tekst“ terug,
  • in [B] worden de door de clientbrowser verzonden waarden gebruikt om de componenten van de weergave bij te werken. Als de gebruiker dus in het invoerveld HTML, met de naam inputText, "jean" heeft ingevoerd, vervangt de waarde "jean" de waarde "tekst". De weergave geeft nu de pagina weer zoals de gebruiker deze heeft gewijzigd en niet meer zoals deze naar de browser is verzonden,
  • in [C] worden de verzonden waarden gecontroleerd. Stel dat de voorgaande component inputText het invoerveld voor een leeftijd is. Dan moet de ingevoerde waarde een geheel getal zijn. De door de browser verzonden waarden zijn altijd van het type String. Hun uiteindelijke type in het M-model dat aan pagina P is gekoppeld, kan heel anders zijn. Er vindt dan een conversie plaats van het type String naar een ander type, T. Deze conversie kan mislukken. In dat geval wordt de verzoek-antwoordcyclus beëindigd en wordt de in [B] opgebouwde pagina P teruggestuurd naar de clientbrowser, samen met foutmeldingen indien de auteur van pagina P deze heeft voorzien. Opgemerkt moet worden dat de gebruiker de pagina terugkrijgt zoals hij deze heeft ingevoerd, zonder dat de ontwikkelaar daar iets voor hoeft te doen. Bij een andere technologie, zoals JSP, moet de ontwikkelaar de pagina P zelf opnieuw opbouwen met de door de gebruiker ingevoerde waarden. De waarde van een component kan ook een validatieproces ondergaan. Nog steeds aan de hand van het voorbeeld van de component inputText, het invoerveld voor de leeftijd, moet de ingevoerde waarde niet alleen een geheel getal zijn, maar ook een geheel getal dat binnen een bepaald bereik [1,N] valt. Als de ingevoerde waarde de conversiefase doorstaat, kan het zijn dat deze de validatiefase niet doorstaat. Ook in dat geval is de vraag-antwoordcyclus voltooid en wordt de pagina P, opgebouwd in [B], teruggestuurd naar de browser van de klant,
  • In [D]: als alle componenten van pagina P de conversie- en validatiefase doorlopen, worden hun waarden toegewezen aan het model M van pagina P. Als de waarde van het invoerveld dat wordt gegenereerd op basis van de volgende tag:

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

"jean" is, dan wordt deze waarde toegewezen aan het formuliermodel van de pagina door de code form.setInputText("jean") uit te voeren. Houd er rekening mee dat in het model M van pagina P de privévelden van M die de waarde van een invoerveld van P opslaan, een set-methode moeten hebben,

  • zodra het model M van pagina P is bijgewerkt met de verzonden waarden, kan de gebeurtenis die de POST van pagina P heeft veroorzaakt, worden verwerkt. Dit is de stap [E]. Merk op dat als de handler van deze gebeurtenis tot de bean M behoort, deze toegang heeft tot de waarden van het formulier P die zijn opgeslagen in de velden van diezelfde bean.
  • In stap [E] wordt een navigatiesleutel teruggestuurd naar de controller JSF. In onze voorbeelden is dit altijd de naam van de weer te geven pagina XHTML, zonder het achtervoegsel .xhtml. Dit is stap [F]. Een andere manier is om een navigatiesleutel terug te sturen die in het bestand [faces-config.xml] wordt opgezocht. Dit geval hebben we beschreven.

Uit het bovenstaande kunnen we concluderen dat:

  • een pagina P de velden C van haar sjabloon M weergeeft met behulp van de methoden [M].getC(),
  • de velden C van het model M van een pagina P worden geïnitialiseerd met de waarden die op pagina P zijn ingevoerd met behulp van de methoden [M].setC(invoer). In deze stap kunnen conversie- en validatieprocessen plaatsvinden die mogelijk mislukken. In dat geval wordt de gebeurtenis die de POST van pagina P heeft veroorzaakt, niet verwerkt en wordt de pagina opnieuw naar de klant teruggestuurd zoals deze door de klant is ingevoerd.

Het sjabloon [Form.java] van de pagina [index.xhtml] ziet er als volgt uit:


package forms;

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


@ManagedBean
@RequestScoped
public class Form {
  
  /** Maakt een nieuw exemplaar van het formulier aan */
  public Form() {
  }
  
  // formuliervelden
  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";
  
  // gebeurtenissen
  public String submit(){
    return null;
  }
  
  // getters en setters
  ...
}

De velden in de regels 16-27 worden op de volgende plaatsen in het formulier gebruikt:

2.5.8. De pagina [index.xhtml]

De pagina [index.xhtml] die de vorige weergave genereert, is als volgt:


<?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">
        <!-- talen -->
        <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">
          <!-- kopteksten -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
          <!-- regel 1 -->
          ...
          <!-- regel 2 -->
          ...
          <!-- regel 3 -->
          ...
          <!-- regel 4 -->
          ...
          <!-- regel 5 -->
          ...
          <!-- regel 6 -->
          ...
          <!-- regel 7 -->
          ...
          <!-- regel 8 -->
          ...
          <!-- regel 9 -->
          ...
          <!-- regel 10 -->
          ...
          <!-- regel 11 -->
          ...
          <!-- regel 12 -->
          ...
        </h:panelGrid>
        <p>
          <h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
        </p>
      </h:form>
    </h:body>
  </f:view>
</html>

We zullen de belangrijkste onderdelen van deze pagina achtereenvolgens bekijken. Let op de algemene structuur van een formulier 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>

De onderdelen van een formulier moeten binnen een <h:form>-tag staan (regels 12-16). De <f:view>-tag (regels 7-18) is nodig als de applicatie wordt geïnternationaliseerd. Bovendien moet een formulier een manier hebben om te worden verzonden (POST), vaak via een link of een knop zoals in regel 14. Het kan ook worden verzonden door talrijke gebeurtenissen (wijziging van een selectie in een lijst, wijziging van het actieve veld, het invoeren van een teken in een invoerveld, ...).

2.5.9. De opmaak van het formulier

Om de kolommen van de tabel in het formulier beter leesbaar te maken, is er een stylesheet bijgevoegd:


  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
</h:head>
  • regel 4: het stylesheet van de pagina wordt gedefinieerd binnen de tag HTML <head>, door middel van de tag:

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

Het stylesheet wordt gezocht in de map [resources]:

In de tag:


<h:outputStylesheet library="css" name="styles.css"/>
  • is 'library' de naam van de map waarin het stylesheet zich bevindt,
  • name is de naam van het stylesheet.

Laten we eens kijken hoe dit stylesheet wordt gebruikt:


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

De tag <h:panelGrid columns="3"/> definieert een tabel met drie kolommen. Met het attribuut columnClasses kunnen deze kolommen worden opgemaakt. De waarden col1, col2, col3 van het attribuut columnClasses verwijzen naar de respectievelijke stijlen van kolom 1, 2 en 3 van de tabel. Deze stijlen worden opgezocht in het stylesheet van de pagina:


.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
}
  • regels 7-9: de stijl met de naam col1,
  • regels 11-13: de stijl met de naam col2,
  • regels 15-17: de stijl met de naam col3,

Deze drie stijlen bepalen de achtergrondkleur van elke kolom.

  • regels 19-23: de stijl ‘entete’ wordt gebruikt om de opmaak van de tekst op de eerste regel van de tabel te bepalen:

          <!-- kopteksten -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
  • regels 1-5: de stijl 'info' wordt gebruikt om de opmaak van de tekst in de eerste kolom van de tabel te bepalen:

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

We zullen niet uitgebreid ingaan op het gebruik van stylesheets, aangezien dit onderwerp op zich al een heel boek zou vullen en de ontwikkeling ervan bovendien vaak aan specialisten wordt toevertrouwd. Toch hebben we ervoor gekozen om er een minimalistische te gebruiken, om te benadrukken dat het gebruik ervan onmisbaar is.

Laten we nu eens kijken hoe de achtergrondafbeelding van de pagina is gedefinieerd:


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

De achtergrondafbeelding wordt ingesteld via het style-attribuut van de <h:body>-tag. Met dit attribuut kunnen stijlelementen worden vastgelegd. De achtergrondafbeelding bevindt zich in de map [resources/images/standard.jpg]:

Deze afbeelding wordt opgehaald via de URL [/mv-jsf2-03/resources/images/standard.jpg]. We zouden dus het volgende kunnen schrijven:


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

/mv-jsf2-03 is de context van de applicatie. Deze context wordt ingesteld door de beheerder van de webserver en kan dus veranderen. Deze context kan worden verkregen via de uitdrukking EL ${request.contextPath}. Daarom geven we de voorkeur aan het volgende style-attribuut:


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

dat in elke context geldig is.

2.5.10. De twee cycli van clientverzoek / serverantwoord van een formulier

Laten we terugkomen op wat al in paragraaf 2.5.7 in een algemeen geval is uitgelegd en dit toepassen op het onderzochte formulier. Dit formulier wordt getest in de klassieke JSF-omgeving:

Hier zullen er geen gebeurtenishandlers of een [métier]-laag zijn. De stappen [2x] zullen dus niet bestaan. Er wordt onderscheid gemaakt tussen het geval waarin formulier F aanvankelijk door de browser wordt opgevraagd en het geval waarin de gebruiker een gebeurtenis in formulier F heeft geactiveerd, waarna dit wordt verwerkt door de controller [Faces Servlet]. Er zijn twee verschillende cycli van clientverzoek / serverantwoord.

  • de eerste, die overeenkomt met het eerste verzoek om de pagina, wordt veroorzaakt door een bewerking GET van de browser op de URL van het formulier,
  • de tweede, die overeenkomt met het verzenden van de op de pagina ingevoerde waarden, wordt geactiveerd door een bewerking POST op diezelfde URL.

Afhankelijk van de aard van het verzoek GET of POST van de browser, verloopt de verwerking van het verzoek door de controller [Faces Servlet] anders.

[cas 1 – demande initiale du formulaire F]

De browser vraagt de URL van de pagina op met een GET. De controller [Faces Servlet] gaat direct door naar de stap [4] voor het weergeven van het antwoord. Het formulier [index.xhtml] wordt geïnitialiseerd door zijn sjabloon [Form.java] en naar de client verzonden, die de volgende weergave ontvangt:

Image

De communicatie tussen client en server (HTTP) verloopt in dit geval als volgt:

Verzoek HTTP van de klant:

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

Op regel 1 zien we de GET van de browser.

Antwoord HTTP van de 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 niet weergegeven: regel 7 wordt gevolgd door een lege regel en de code HTML van het formulier. Deze code wordt door de browser geïnterpreteerd en weergegeven.

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

De gebruiker vult het formulier in en verzendt het via de knop [Valider]. De browser vraagt vervolgens de URL van het formulier op met een POST. De controller [Faces Servlet] verwerkt dit verzoek, werkt het model [Form.java] van het formulier [index.xhtml] bij en stuurt het formulier [index.xhtml], dat met dit nieuwe model is bijgewerkt, opnieuw terug. Laten we deze cyclus aan de hand van een voorbeeld bekijken:

Image

Hierboven heeft de gebruiker zijn gegevens ingevoerd en gevalideerd. Als reactie ontvangt hij de volgende weergave:

Image

De HTTP-uitwisselingen tussen client en server zijn in dit geval als volgt:

Verzoek HTTP van de 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  

Op regel 1 staat de POST die door de browser is gegenereerd. Op regel 14 staan de door de gebruiker ingevoerde waarden. Hier is bijvoorbeeld de tekst te zien die in het invoerveld is geplaatst:

formulaire%3AinputText=nouveau+texte

In regel 14 is het verborgen veld javax.faces.ViewState verzonden. Dit veld vertegenwoordigt, in gecodeerde vorm, de status van het formulier zoals het oorspronkelijk naar de browser werd verzonden tijdens de eerste GET.

Antwoord HTTP van de 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

Hoewel hier niet weergegeven, wordt regel 6 gevolgd door een lege regel en de code HTML van het bijgewerkte formulier op basis van het nieuwe sjabloon afkomstig van POST.

We bekijken nu de verschillende onderdelen van dit formulier.

2.5.11. Tag <h:inputText>

De tag <h:inputText> genereert een tag HTML <input type="text" ...>.

Laten we de volgende code eens bekijken:


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

en het bijbehorende sjabloon [Form.java]:


  private String inputText="texte";

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

Wanneer de pagina [index.html] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1],
  • met de tag <h:panelGroup> (regels 3-6) kunnen meerdere elementen worden gegroepeerd in één cel van de tabel die wordt gegenereerd door de tag <h:panelGrid> in regel 20 van de volledige code van de pagina (zie paragraaf 2.5.8). De tekst [2] wordt gegenereerd door regel 4. Het invoerveld [3] wordt gegenereerd door regel [5]. Hier is de methode getInputText van [Form.java] (regels 3-5 van de Java-code) gebruikt om de tekst van het invoerveld te genereren,
  • regel 7 van de code XHTML genereert [4]. Opnieuw wordt de methode getInputText van [Form.java] gebruikt om de tekst [4] te genereren.

De stroom HTML die door de pagina XHTML wordt gegenereerd, is als volgt:


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

De tags HTML <tr> en <td> worden gegenereerd door de tag <h:panelGrid> die wordt gebruikt om de tabel van het formulier te genereren.

Laten we nu hieronder een waarde invoeren in het invoerveld [1] en het formulier verzenden met de knop [Valider] [2]. Als reactie krijgen we de pagina [3, 4] te zien:

De waarde van het veld [1] wordt als volgt verzonden:

formulaire%3AinputText=nouveau+texte

In [2] wordt het formulier gevalideerd met de volgende knop:


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

De tag <h:commandButton> heeft geen action-attribuut. In dit geval wordt er geen gebeurtenishandler aangeroepen en wordt er geen navigatieregel toegepast. Na verwerking wordt dezelfde pagina teruggestuurd. Laten we de verwerkingscyclus nog eens bekijken:

ABCDEF

Image

  • in [A] wordt pagina P hersteld zoals deze was verzonden. Dit betekent dat de component met id inputText wordt hersteld met de oorspronkelijke waarde "tekst",
  • in [B] worden de door de browser verzonden waarden (door de gebruiker ingevoerd) toegewezen aan de componenten van pagina P. Hier krijgt de component met id inputText de waarde "een nieuwe tekst",
  • in [C] vinden de conversies en validaties plaats. Hier zijn er geen. In het model M is het veld dat gekoppeld is aan de component met id inputText als volgt:

private String inputText="texte";

Aangezien de ingevoerde waarden van het type String zijn, hoeft er geen conversie plaats te vinden. Bovendien is er geen validatieregel aangemaakt. Deze zullen we later opstellen.

  • In [D] worden de ingevoerde waarden aan het model toegewezen. Het veld inputText van [Form.java] krijgt de waarde "een nieuwe tekst",
  • in [E] gebeurt er niets, omdat er geen gebeurtenishandler is gekoppeld aan de knop [Valider].
  • In [F] wordt pagina P opnieuw naar de client verzonden, omdat de knop [Valider] geen action-attribuut heeft. De volgende regels van [index.xhtml] worden vervolgens uitgevoerd:

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

In de regels 5 en 7 wordt de waarde van het veld inputText uit het sjabloon gebruikt, die nu "een nieuwe tekst" is. Vandaar de volgende weergave:

Image

2.5.12. Tag <h:inputSecret>

De tag <h:inputSecret> genereert een tag HTML <input type="password" ...>. Dit is een invoerveld dat vergelijkbaar is met dat van de tag JSF <h:inputText>, met dit verschil dat elk teken dat de gebruiker invoert visueel wordt vervangen door een *.

Laten we de volgende code eens bekijken:


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

en het bijbehorende sjabloon in [Form.java]:


private String inputSecret="secret";

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1]
  • de tekst [2] wordt gegenereerd door regel 4. Het invoerveld [3] wordt gegenereerd door regel [5]. Normaal gesproken had de methode getInputSecret van [Form.java] moeten worden gebruikt om de tekst van het invoerveld te genereren. Er is een uitzondering wanneer het om een "wachtwoord"-veld gaat. De tag <h:inputSecret> dient alleen om een invoer te lezen, niet om deze weer te geven.
  • regel 7 van de code XHTML genereert [4]. Hier is de methode getInputSecret van [Form.java] gebruikt om de tekst [4] te genereren (zie regel 1 van de Java-code).

De door de pagina XHTML gegenereerde stroom HTML is als volgt:


<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>
  • regel 3: de tag HTML <input type= "password " .../>, gegenereerd door de tag JSF <h:inputSecret>

Laten we nu hieronder een waarde invoeren in het invoerveld [1] en het formulier verzenden met de knop [Valider] [2]. We krijgen dan de pagina [3] te zien:

De waarde van het veld [1] wordt als volgt verzonden:

formulaire%3AinputSecret=mdp

De validatie van het formulier door [2] heeft geleid tot een update van het model [Form.java] door de invoer [1]. Het veld inputSecret van [Form.java] kreeg vervolgens de waarde mdp. Omdat het formulier [index.xhtml] geen navigatieregels of gebeurtenishandlers heeft gedefinieerd, wordt het opnieuw weergegeven nadat het sjabloon is bijgewerkt. We komen dan weer terecht bij de weergave zoals bij de eerste aanvraag van de pagina [index.xhtml], waarbij alleen de waarde van het veld inputSecret in het sjabloon is gewijzigd in [3].

2.5.13. Tag <h:inputTextArea>

De tag <h:inputTextArea> genereert een tag HTML <textarea ...>tekst</textarea>. Dit is een invoerveld dat vergelijkbaar is met dat van de tag JSF <h:inputText>, behalve dat je hier meerdere regels tekst kunt typen.

Laten we de volgende code eens bekijken:


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

en het bijbehorende sjabloon in [Form.java]:


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

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1],
  • de tekst [2] wordt gegenereerd door regel 4. Het invoerveld [3] wordt gegenereerd door de regel [5]. De inhoud ervan is gegenereerd door de methode getInputTextArea van het model aan te roepen, die de waarde heeft gerenderd die is gedefinieerd in regel 1 van de bovenstaande Java-code,
  • regel 7 van de code XHTML genereert [4]. Hier is opnieuw de methode getInputTextArea van [Form.java] gebruikt. De tekenreeks "regel1\nregel2" bevatte regeleinden \n. Die zijn er nog steeds. Maar omdat ze in een HTML-stream zijn ingevoegd, worden ze door browsers weergegeven als spaties. De tag HTML <textarea>, die [3] weergeeft, interpreteert de regeleinden daarentegen wel correct.

De feed HTML die door de pagina XHTML wordt gegenereerd, ziet er als volgt uit:


<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>
  • regels 3-5: de tag HTML <textarea>...</textarea>, gegenereerd door de tag JSF <h:inputTextArea>

Laten we nu hieronder een waarde invoeren in het invoerveld [1] en het formulier verzenden met de knop [Valider] [2]. Als reactie krijgen we de pagina [3] te zien:

De waarde van het verzonden veld [1] is als volgt:

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

De validatie van het formulier door [2] heeft geleid tot een update van het sjabloon [Form.java] door de invoer [1]. Het veld textArea van [Form.java] kreeg vervolgens de waarde "Tutorial JSF\ndeel1". Bij het opnieuw weergeven van [index.xhtml] is te zien dat het veld textArea van het sjabloon inderdaad is bijgewerkt naar [3].

2.5.14. Tag <h:selectOneListBox>

De tag <h:selectOneListBox> genereert een tag HTML <select>...</select>. Visueel genereert deze een vervolgkeuzelijst of een lijst met schuifbalk.

Laten we de volgende code eens bekijken:


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

en het bijbehorende sjabloon in [Form.java]:


private String selectOneListBox1="2";

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1]
  • de tekst [2] wordt gegenereerd door regel 4. De vervolgkeuzelijst [3] wordt gegenereerd door de regels [5-9]. Door de waarde van het attribuut size="1" wordt er slechts één item in de lijst weergegeven. Als dit attribuut ontbreekt, is de standaardwaarde van het attribuut size 1. De elementen van de lijst zijn gegenereerd door de tags <f:selectItem> in de regels 6-8. Deze tags hebben de volgende syntaxis:

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

De waarde van het attribuut itemLabel is wat er in de lijst wordt weergegeven. De waarde van het attribuut itemValue is de waarde van het element. Deze waarde wordt naar de controller [Faces Servlet] verzonden als het element in de vervolgkeuzelijst wordt geselecteerd.

Het element dat wordt weergegeven in [3] is bepaald door de methode getSelectOneListBox1() aan te roepen (regel 5). Het verkregen resultaat „2“ (regel 1 van de Java-code) heeft ervoor gezorgd dat het element op regel 7 van de vervolgkeuzelijst werd weergegeven, omdat het attribuut itemValue de waarde „2“ heeft,

  • regel 11 van de code XHTML genereert [4]. Hier is opnieuw de methode getSelectOneListBox1 van [Form.java] gebruikt.

De stroom HTML die door de pagina XHTML wordt gegenereerd, is als volgt:


<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>
  • regels 3 en 7: de tag HTML <select ...>...</select> die is gegenereerd door de tag JSF <h:selectOneListBox>,
  • regels 4-6: de tags HTML <option ...> ... </option> die worden gegenereerd door de tags JSF <f:selectItem>,
  • regel 5: het feit dat het element met value="2" in de lijst is geselecteerd, komt tot uiting in de aanwezigheid van het attribuut selected="selected".

Laten we nu hieronder een nieuwe waarde uit de lijst kiezen en het formulier verzenden met de knop. Als reactie krijgen we de pagina:

De waarde van het verzonden veld [1] is als volgt:

formulaire%3AselectOneListBox1=3

De validatie van het formulier door [2] heeft geleid tot een update van het model [Form.java] door de invoer [1]. Het element HTML


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

is geselecteerd. De browser heeft de tekenreeks "3" verzonden als waarde van de component JSF die de vervolgkeuzelijst heeft gegenereerd:


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

De controller JSF zal de methode setSelectOneListBox1("3") gebruiken om het model van de vervolgkeuzelijst bij te werken. Na deze update zal het veld van het model [Form.java]


        private String selectOneListBox1;

de waarde "3".

Wanneer de pagina [index.xhtml] na de verwerking opnieuw wordt weergegeven, zorgt deze waarde ervoor dat de bovenstaande weergave [3,4] wordt getoond:

  • deze bepaalt welk item uit de vervolgkeuzelijst moet worden weergegeven ([3]),
  • de waarde van het veld selectOneListBox1 wordt weergegeven in [4].

Laten we eens kijken naar een variant van de tag <h:selectOneListBox>:


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

Het sjabloon in [Form.java] van de tag <h:selectOneListBox> op regel 5 is als volgt:


  private String selectOneListBox2="3";

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1],
  • de tekst [2] wordt gegenereerd door regel 4. De lijst met schuifbalk [3] wordt gegenereerd door de regels van [5-11]. Het is de waarde van het attribuut size="3" die ervoor zorgt dat we een lijst met schuifbalk krijgen in plaats van een vervolgkeuzelijst. De elementen van de lijst zijn gegenereerd door de tags <f:selectItem> in de regels 6-8,

Het geselecteerde element in [3] is bepaald door de methode getSelectOneListBox2() aan te roepen (regel 5). Het verkregen resultaat „3“ (regel 1 van de Java-code) heeft ervoor gezorgd dat het element op regel 8 van de lijst werd weergegeven, omdat het attribuut itemValue de waarde „3“ heeft,

  • regel 13 van de code XHTML genereert [4]. Hier is opnieuw de methode getSelectOneListBox2 van [Form.java] gebruikt.

De stroom HTML die door de pagina XHTML wordt gegenereerd, is als volgt:


<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>
  • regel 6: het feit dat het element met value="3" in de lijst is geselecteerd, komt tot uiting in de aanwezigheid van het attribuut selected="selected".

Laten we nu hieronder met [1] een nieuwe waarde uit de lijst kiezen en het formulier verzenden met de knop [Valider] [2]. Als reactie krijgen we de pagina [3]:

De verzonden waarde voor het veld [1] is als volgt:

formulaire%3AselectOneListBox2=5

De validatie van het formulier door [2] heeft geleid tot een update van het sjabloon [Form.java] door de invoer [1]. Het element HTML


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

is geselecteerd. De browser heeft de tekenreeks "5" verzonden als waarde van de component JSF die de vervolgkeuzelijst heeft gegenereerd:


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

De controller JSF zal de methode setSelectOneListBox2("5") gebruiken om het sjabloon van de lijst bij te werken. Na deze update zal het veld


        private String selectOneListBox2;

de waarde "5".

Wanneer de pagina [index.xhtml] na de verwerking opnieuw wordt weergegeven, zorgt deze waarde ervoor dat de bovenstaande weergave [3,4] verschijnt:

  • deze bepaalt welk element uit de lijst moet worden geselecteerd ([3]),
  • de waarde van het veld selectOneListBox2 wordt weergegeven in [4].

2.5.15. Tag <h:selectManyListBox>

De tag <h:selectmanyListBox> genereert een tag HTML <select multiple="multiple">...</select> waarmee de gebruiker meerdere elementen uit een lijst kan selecteren.

Laten we de volgende code eens bekijken:


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

en het bijbehorende sjabloon in [Form.java]:


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

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de weergegeven pagina als volgt:

  • regel 2 van de code XHTML genereert [1]
  • de tekst [2] wordt gegenereerd door regel 4. De lijst [3] wordt gegenereerd door de regels [5-11]. Het attribuut size="3" zorgt ervoor dat de lijst op een bepaald moment drie van deze elementen weergeeft. De geselecteerde elementen in de lijst zijn bepaald door de methode getSelectManyListBox() (regel 5) van het Java-model aan te roepen. Het verkregen resultaat {"1","3"} (regel 1 van de Java-code) is een array van elementen van het type String. Elk van deze elementen dient om een van de elementen uit de lijst te selecteren. Hier worden de elementen in de regels 6 en 10 geselecteerd waarvan het attribuut itemValue in de array {"1","3"} voorkomt. Dit wordt weergegeven door [3].
  • regel 14 van de code XHTML genereert [4]. Hier wordt geen gebruik gemaakt van de methode getSelectManyListBox van het Java-model van de lijst, maar van de volgende methode getSelectManyListBoxValue:

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

Als we de methode getSelectManyListBox hadden aangeroepen, zouden we een array van String hebben gekregen. Om dit element op te nemen in de stroom HTML, zou de controller zijn methode toString hebben aangeroepen. Deze methode geeft voor een array echter alleen de „hashcode“ ervan terug en niet de lijst met elementen, zoals we willen. Daarom gebruiken we de hierboven genoemde methode getSelectManyListBoxValue om een tekenreeks te verkrijgen die de inhoud van de array weergeeft,

  • regel 12 van de code XHTML genereert de knop [5]. Wanneer op deze knop wordt geklikt, wordt de JavaScript-code van het onclick-attribuut uitgevoerd. Deze wordt ingebed in de pagina HTML, die wordt gegenereerd door de code JSF. Om dit te begrijpen, moeten we weten wat de exacte aard hiervan is.

De door de pagina XHTML gegenereerde stream HTML is als volgt:


<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>
  • regels 3 en 9: de tag HTML <select multiple="multiple"...>...</select>, gegenereerd door de tag JSF <h:selectManyListBox>. Het is de aanwezigheid van het attribuut `multiple` die aangeeft dat het om een lijst met meervoudige selectie gaat,
  • en doordat het model van de lijst de String-array {"1","3"} is, hebben de lijstelementen op regel 4 (value="1") en 6 (value="3") het attribuut selected="selected",
  • regel 10: wanneer je op de knop [Raz] klikt, wordt de JavaScript-code van het onclick-attribuut uitgevoerd. De pagina wordt in de browser weergegeven als een boomstructuur van objecten die vaak DOM (Document Object Model) wordt genoemd. Elk object in de boomstructuur is voor de JavaScript-code toegankelijk via het attribuut name. De lijst op regel 3 van de bovenstaande code HTML heet formulier:selectManyListBox. Het formulier zelf kan op verschillende manieren worden aangeduid. Hier wordt het aangeduid met de notatie this.form, waarbij `this` verwijst naar de knop [Raz] en this.form naar het formulier waarin deze knop zich bevindt. De lijst formulier:selectManyListBox bevindt zich in hetzelfde formulier. De notatie this.form['formulaire:selectManyListBox'] verwijst dus naar de locatie van de lijst in de componentboom van het formulier. Het object dat een lijst vertegenwoordigt, heeft een attribuut selectedIndex met als waarde het nummer van het geselecteerde element in de lijst. Dit nummer begint bij 0 om het eerste element van de lijst aan te duiden. De waarde -1 geeft aan dat er geen element in de lijst is geselecteerd. De JavaScript-code die de waarde -1 toekent aan het attribuut selectedIndex zorgt ervoor dat alle elementen in de lijst worden gedeselecteerd, mochten die er zijn.

Laten we nu hieronder nieuwe waarden uit de lijst selecteren (om meerdere elementen in de lijst te selecteren, houdt u de Ctrl-toets ingedrukt terwijl u klikt) en het formulier verzenden met de knop [Valider] [2]. We krijgen als antwoord de pagina [3,4]:

De waarde van het verzonden veld [1] is als volgt:

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

De validatie van het formulier door [2] heeft geleid tot een update van het sjabloon [Form.java] door de invoer [1]. De elementen HTML


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

zijn geselecteerd. De browser heeft de drie tekenreeksen "3", "4" en "5" verzonden als waarden van de component JSF die de vervolgkeuzelijst heeft gegenereerd:


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

De methode setSelectManyListBox van het model wordt gebruikt om dit model bij te werken met de door de browser verzonden waarden:


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

In regel 3 zien we dat de parameter van de methode een array van String is. In dit geval is dat de array {"3","4","5"}. Na deze update is het veld


        private String[] selectManyListBox;

nu de array {"3","4","5"} bevat.

Wanneer de pagina [index.xhtml] na de verwerking opnieuw wordt weergegeven, zorgt deze waarde ervoor dat de bovenstaande weergave [3,4] verschijnt:

  • deze bepaalt welke elementen uit de lijst moeten worden geselecteerd ([3]),
  • de waarde van het veld selectManyListBox wordt weergegeven in [4].

2.5.16. Tag <h:selectOneMenu>

De tag <h:selectOneMenu> is identiek aan de tag <h:selectOneListBox size="1">. In het voorbeeld is de uitgevoerde code JSF als volgt:


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

Het sjabloon van de tag <h:selectOneMenu> in [Form.java] is als volgt:


  private String selectOneMenu="1";

Bij de eerste aanvraag van de pagina [index.xhtml] genereert de bovenstaande code de weergave:

Een voorbeeld van een uitvoering zou er als volgt uit kunnen zien:

De verzonden waarde voor het veld [1] is als volgt:

formulaire%3AselectOneMenu=4

2.5.17. Tag <h:selectManyMenu>

De tag <h:selectManyMenu> is identiek aan de tag <h:selectManyListBox size="1">. De code JSF die in het voorbeeld wordt uitgevoerd, is als volgt:


<!-- regel 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"/>

Het sjabloon van de tag <h:selectManyMenu> in [Form.java] is als volgt:


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

Op basis van de oorspronkelijke aanvraag van de pagina [index.xhtml] genereert de bovenstaande code de volgende pagina:

De lijst [1] bevat de teksten "één", ..., "vijf", waarbij de elementen "één" en "twee" zijn geselecteerd. De gegenereerde code HTML is als volgt:


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

Bovenstaande regels 4 en 5 laten zien dat de elementen "un" en "deux" zijn geselecteerd (aanwezigheid van het attribuut selected).

Het is moeilijk om een schermafbeelding van een uitvoervoorbeeld te geven, omdat de geselecteerde elementen in het menu niet kunnen worden weergegeven. De lezer wordt uitgenodigd om de test zelf uit te voeren (om meerdere elementen in de lijst te selecteren, houdt u de Ctrl-toets ingedrukt terwijl u klikt).

2.5.18. Tag <h:inputHidden>

De tag <h:inputHidden> heeft geen visuele weergave. Deze dient uitsluitend om een tag <input type="hidden" value="..."/> in de HTML-stroom van de pagina in te voegen. Wanneer ze zijn opgenomen in een <h:form>-tag, maken hun waarden deel uit van de waarden die naar de server worden verzonden wanneer het formulier wordt verzonden. Omdat het formuliervelden zijn die de gebruiker niet ziet, worden ze verborgen velden genoemd. Het voordeel van deze velden is dat ze geheugen vrijhouden tussen de verschillende verzoek-/antwoordcycli van dezelfde client:

  • de client vraagt een formulier F aan. De server stuurt dit naar de client en plaatst een stukje informatie I in een verborgen veld C, in de vorm <h:inputHidden id="C" value="I"/>,
  • wanneer de klant formulier F heeft ingevuld en naar de server verstuurt, wordt de waarde I van veld C teruggestuurd naar de server. Deze kan dan de informatie I terugvinden die hij in de pagina had opgeslagen. Zo is er een geheugen gecreëerd tussen de twee verzoek-antwoordcycli,
  • JSF maakt zelf gebruik van deze techniek. De informatie I die het in formulier F opslaat, is de waarde van alle componenten daarvan. Hiervoor gebruikt het het volgende verborgen veld:

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

Het verborgen veld heet javax.faces.ViewState en de waarde ervan is een tekenreeks die in gecodeerde vorm de waarde weergeeft van alle componenten van de pagina die naar de client is verzonden. Wanneer de client de pagina terugstuurt nadat er gegevens in het formulier zijn ingevoerd, wordt het verborgen veld javax.faces.ViewState samen met de ingevoerde waarden teruggestuurd. Hierdoor kan de controller JSF de pagina reconstrueren zoals deze oorspronkelijk was verzonden. Dit mechanisme is uitgelegd op pagina 72.

De code JSF uit het voorbeeld is als volgt:


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

Het sjabloon van de tag <h:inputHidden> in [Form.java] is als volgt:


  private String inputHidden="initial";

Dit resulteert in de volgende weergave bij de eerste aanvraag van de pagina [index.xhtml]:

  • regel 2 genereert [1], regel 4 [2]. Regel 3 genereert geen visueel element.

De gegenereerde code HTML is als volgt:


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

Bij het verzenden van het formulier met POST wordt de „oorspronkelijke” waarde van het veld met de naam formulaire:inputHidden uit regel 3 samen met de andere waarden van het formulier verzonden. Het veld


  private String inputHidden;

wordt bijgewerkt met deze waarde, die overeenkomt met de oorspronkelijke waarde. Deze waarde wordt opgenomen in de nieuwe pagina die naar de klant wordt teruggestuurd. Het resultaat is dus altijd de bovenstaande schermafbeelding.

De verzonden waarde voor het verborgen veld is als volgt:

formulaire%3AinputHidden=initial

2.5.19. Tag <h:selectBooleanCheckBox>

De tag <h:selectBooleanCheckBox> genereert een tag HTML <input type="checkbox" ...>.

Laten we de volgende JSF-code eens bekijken:


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

Het sjabloon van de tag <h:selectBooleanCheckbox> uit regel 5 hierboven in [Form.java] is als volgt:


  private boolean selectBooleanCheckbox=true;

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1],
  • de tekst [2] wordt gegenereerd door regel 4. Het selectievakje [3] wordt gegenereerd door regel [5]. Hier is de methode getSelectBooleanCheckbox van [Form.java] gebruikt om het selectievakje al dan niet aan te vinken. Omdat de methode de booleaanse waarde op ‘true’ zet (zie Java-code), is het selectievakje aangevinkt,
  • regel 7 van de code XHTML genereert [4]. Opnieuw wordt de methode getSelectBooleanCheckbox van [Form.java] gebruikt om de tekst [4] te genereren.

De stroom HTML, gegenereerd door de voorgaande code JSF, is als volgt:


<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] zien we de tag HTML <input type="checkbox"> die is gegenereerd. Door de waarde true van het bijbehorende sjabloon is het attribuut checked="checked" aan de tag toegevoegd. Hierdoor is het selectievakje aangevinkt.

Laten we nu hieronder het selectievakje [1] uitschakelen, het formulier [2] verzenden en het verkregen resultaat [3, 4] bekijken:

Omdat het selectievakje niet is aangevinkt, is er geen waarde ingevoerd voor het veld [1].

De validatie van het formulier door [2] heeft ertoe geleid dat het model [Form.java] is bijgewerkt door de invoer [1]. Het veld selectBooleanCheckbox van [Form.java] heeft vervolgens de waarde 'false' gekregen. Uit de herweergave van [index.xhtml] blijkt dat het veld selectBooleanCheckbox van het model inderdaad is bijgewerkt naar [3] en [4]. Het is hier interessant om op te merken dat JSF dankzij het verborgen veld javax.faces.ViewState kon vaststellen dat het aanvankelijk aangevinkte selectievakje door de gebruiker was uitgeschakeld. De waarde van een uitgeschakeld selectievakje maakt namelijk geen deel uit van de waarden die door de browser worden verzonden. Dankzij de componentboom die is opgeslagen in het verborgen veld javax.faces.ViewState, kan JSF vaststellen dat er een selectievakje met de naam „selectBooleanCheckbox“ in het formulier stond en dat de waarde daarvan niet voorkomt in de waarden die door de clientbrowser zijn verzonden. Hieruit kan het afleiden dat het selectievakje in het verzonden formulier niet was aangevinkt, waardoor het de booleaanse waarde false aan het bijbehorende Java-model kan toewijzen:


  private boolean selectBooleanCheckbox;

2.5.20. Tag <h:selectManyCheckBox>

De tag <h:selectManyCheckBox> genereert een groep selectievakjes en dus meerdere tags HTML <input type="checkbox" ...>. Deze tag is de tegenhanger van de tag <h:selectManyListBox>, met dit verschil dat de te selecteren elementen worden weergegeven in de vorm van aangrenzende selectievakjes in plaats van in de vorm van een lijst. Wat gezegd is over de tag <h:selectManyListBox> geldt ook hier.

Laten we de volgende code JSF eens bekijken:


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

Het sjabloon van de tag <h:selectManyCheckbox> uit regel 5 hierboven in [Form.java] is als volgt:


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

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de verkregen pagina als volgt:

  • regel 2 van de code XHTML genereert [1],
  • de tekst [2] wordt gegenereerd door regel 4. De selectievakjes [3] worden gegenereerd door de regels 5-10. Voor elk daarvan geldt:
  • bepaalt het attribuut itemLabel de tekst die naast het selectievakje wordt weergegeven;
  • het attribuut itemvalue bepaalt de waarde die naar de server wordt verzonden als het selectievakje is aangevinkt;

Het model voor de vier selectievakjes is het volgende Java-veld:


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

Deze tabel bepaalt:

  • welke selectievakjes er aangevinkt moeten zijn wanneer de pagina wordt weergegeven. Dit gebeurt via hun waarde, c.a.d, en hun veld itemValue. Hierboven worden de selectievakjes met waarden in de tabel {"1","3"} aangevinkt. Dit is te zien op de schermafbeelding hierboven;
  • wanneer de pagina wordt verzonden, ontvangt het sjabloon selectManyCheckbox de array met de waarden van de selectievakjes die de gebruiker heeft aangevinkt. Dit zullen we straks bekijken;
  • regel 12 van de code XHTML genereert [4]. Het is de volgende methode getSelectManyCheckboxValue die [4] heeft gegenereerd:

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

De HTML-stroom die door de voorgaande code JSF is gegenereerd, is als volgt:


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

Er zijn vier HTML-tags <input type="checkbox" ...> gegenereerd. De tags op regel 3 en 7 hebben het attribuut checked="checked", waardoor ze aangevinkt worden weergegeven. Merk op dat ze allemaal hetzelfde attribuut name="formulier:selectManyCheckbox" hebben, met andere woorden: de vier velden HTML hebben dezelfde naam. Als de selectievakjes op regel 5 en 9 door de gebruiker worden aangevinkt, verstuurt de browser de waarden van de vier selectievakjes in de volgende vorm:

formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

en het sjabloon van de vier selectievakjes


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

ontvangt de array {"2","4"}.

Laten we dit hieronder eens bekijken. In [1] voeren we de wijziging door, in [2] valideren we het formulier. In [3] is het verkregen resultaat:

De verzonden waarden voor de velden in [1] zijn als volgt:

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

2.5.21. Tag <h:selectOneRadio>

De tag <h:selectOneRadio> genereert een groep keuzerondjes die elkaar uitsluiten.

Laten we de volgende code JSF eens bekijken:


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

Het sjabloon van de tag <h:selectOneRadio> uit regel 5 hierboven is als volgt in [Form.java]:


  private String selectOneRadio="2";

Wanneer de pagina [index.xhtml] voor het eerst wordt opgevraagd, is de weergegeven weergave als volgt:

  • regel 2 van de code XHTML genereert [1],
  • De tekst [2] wordt gegenereerd door regel 4. De keuzerondjes [3] worden gegenereerd door de regels 5-10. Voor elk daarvan geldt:
  • bepaalt het attribuut itemLabel de tekst die naast de keuzeknop wordt weergegeven;
  • het attribuut itemvalue bepaalt de waarde die naar de server wordt verzonden als de keuzeknop is aangevinkt;

Het model voor de vier keuzerondjes is het volgende Java-veld:


  private String selectOneRadio="2";

Dit sjabloon bepaalt:

  • wanneer de pagina wordt weergegeven, welk keuzerondje als enige moet worden aangevinkt. Dit gebeurt via de waarde c.a.d en het veld itemValue. Hierboven zal de keuzeknop met de waarde "2" geselecteerd zijn. Dit is te zien op de schermafbeelding hierboven;
  • wanneer de pagina wordt verzonden, krijgt het sjabloon selectOneRadio de waarde van de selectieknop die is aangevinkt. Dit gaan we straks bekijken,
  • regel 12 van de code XHTML genereert [4].

De HTML-feed die door de voorgaande code JSF wordt gegenereerd, ziet er als volgt uit:


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

Er zijn vier HTML-tags <input type="radio" ...> gegenereerd. De tag op regel 8 heeft het attribuut checked="checked", waardoor het bijbehorende keuzerondje aangevinkt wordt weergegeven. Merk op dat de tags allemaal hetzelfde attribuut name="formulier:selectOneRadio" hebben, met andere woorden: de vier velden HTML hebben dezelfde naam. Dit is de voorwaarde voor een groep exclusieve keuzerondjes: wanneer er één is aangevinkt, zijn de andere niet aangevinkt.

Hieronder, in [1], vinken we een van de keuzerondjes aan, in [2] verzenden we het formulier, in [3] het verkregen resultaat:

De verzonden waarde voor het veld [1] is als volgt:

formulaire%3AselectOneRadio=4

2.6. Voorbeeld mv-jsf2-04: dynamische lijsten

2.6.1. De applicatie

De applicatie is dezelfde als eerder:

De enige wijzigingen hebben betrekking op de manier waarop de elementen van de lijsten in de zones [1] en [2] worden gegenereerd. Ze worden hier dynamisch gegenereerd door Java-code, terwijl ze in de vorige versie 'vast' in de code van de pagina JSF waren vastgelegd.

2.6.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

Het project [mv-jsf2-04] is identiek aan het project [mv-jsf2-03], met de volgende verschillen:

  • in [1], op de pagina JSF, worden de elementen van de lijsten niet langer „vast“ in de code geschreven,
  • in [2] wordt het sjabloon van de pagina JSF [1] aangepast,
  • in [3] wordt een van de berichten aangepast.

2.6.3. De pagina [index.xhtml] en het bijbehorende sjabloon [Form.java]

De pagina JSF [index.xhtml] wordt als volgt:


<?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">
        <!-- talen -->
        <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">
...
          <!-- regel 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}"/>
          <!-- regel 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}"/>
          <!-- regel 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}"/>
          <!-- regel 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}"/>
          <!-- regel 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"/>
...
          <!-- regel 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}"/>
          <!-- regel 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>

De aangebrachte wijzigingen worden geïllustreerd door de regels 26-28. Waar voorheen de code stond:


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

staat nu deze:


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

De drie tags <f:selectItem> in de regels 2-4 zijn vervangen door de enkele tag <f:selectItems> in regel b. Deze tag heeft een value-attribuut waarvan de waarde een verzameling elementen van het type javax.faces.model.SelectItem is. Hierboven wordt de waarde van het value-attribuut verkregen door de volgende methode [form].getSelectOneListbox1Items aan te roepen:


  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 regel 1 retourneert de methode getSelectOneListbox1Items een array van elementen van het type javax.faces.model.SelectItem, die is samengesteld door de privémethode getItems uit regel 5. Merk op dat de methode getSelectOneListbox1Items niet de getter is van een privéveld selectOneListBox1Items,
  • de klasse javax.faces.model.SelectItem heeft verschillende constructors.

Image

We gebruiken regel 8 van de methode getItems, de constructor SelectItem(Object value, String label) die overeenkomt met de tag JSF


    <f:selectItem itemValue="value" labelValue="label"/>
  • regels 5-10: de methode getItems(String label, int qte) maakt een array aan met qte elementen van het type SelectItem, waarbij element i wordt verkregen via de constructor SelectItem(i, label+i).

De code JSF


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

wordt dan functioneel gelijk aan de volgende 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>

Hetzelfde geldt voor alle andere lijsten op de pagina JSF. In het sjabloon [Form.java] zijn dan ook de volgende nieuwe methoden te vinden:


  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. Het berichtenbestand

Er wordt slechts één bericht gewijzigd:

[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

De lezer wordt uitgenodigd om deze nieuwe versie te testen.

Meestal zijn de dynamische elementen van een formulier het resultaat van een bedrijfsspecifieke verwerking of zijn ze afkomstig uit een database:

Laten we de oorspronkelijke aanvraag van de pagina JSF [index.xhtml] door een GET van de browser eens bekijken:

  • de pagina JSF wordt opgevraagd via [1],
  • de controller [Faces Servlet] vraagt om de weergave ervan in [3]. De engine JSF die de pagina verwerkt, maakt gebruik van het model [Form.java] van die pagina, bijvoorbeeld de methode getSelectOneListBox1Items. Deze methode zou heel goed een array van elementen van het type SelectItem kunnen retourneren, op basis van informatie die in een database is opgeslagen. Hiervoor zou de methode gebruikmaken van de laag [métier] [2b].

2.7. Voorbeeld mv-jsf2-05: navigatie – sessie – uitzonderingsbeheer

2.7.1. De applicatie

De applicatie is dezelfde als eerder, behalve dat het formulier nu de vorm heeft van een wizard met meerdere pagina’s:

  • in [1], pagina 1 van het formulier – is ook te bereiken via link 1 van [2]
  • in [2], een groep van 5 links.
  • naar [3], pagina 2 van het formulier, bereikbaar via link 2 van [2]
  • in [4], pagina 3 van het formulier, bereikbaar via link 3 van [2]
  • in [5], de pagina die wordt geopend via de link Een uitzondering genereren van [2]
  • in [6], de pagina die wordt geopend via link 4 van [2]. Deze pagina geeft een overzicht van de gegevens die op pagina's 1 tot en met 3 zijn ingevoerd.

2.7.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

Het project [mv-jsf2-05] introduceert twee nieuwe elementen:

  1. in [1] is de pagina JSF [index.xhtml] opgesplitst in drie pagina's [form1.xhtml, form2.xhtml, form3.xhtml], waarover de invoer is verdeeld. De pagina [form4.xhtml] is een kopie van de pagina [index.xhtml] uit het vorige project. In [2] blijft de klasse [Form.java] ongewijzigd. Deze zal als sjabloon dienen voor de vier voorgaande pagina’s JSF,
  2. in [3] wordt een pagina [exception.xhtml] toegevoegd: deze wordt gebruikt wanneer er een uitzondering optreedt in de applicatie.

2.7.3. De pagina’s [form.xhtml] en hun sjabloon [Form.java]

2.7.3.1. De code van de pagina's XHTML

De pagina JSF [form1.xhtml] is als volgt:


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

en komt overeen met de volgende weergave:

Let op de volgende punten:

  • regel 16: de tabel, die voorheen drie kolommen had, heeft er nu nog maar twee. Kolom 3, waarin de waarden van het model werden weergegeven, is verwijderd. Deze worden nu weergegeven door [form4.xhtml],
  • regels 40-46: een tabel met zes links. De links in de regels 44 en 46 hebben een statische navigatie: hun attribuut `action` is hard gecodeerd. De overige links hebben een dynamische navigatie: hun attribuut `action` verwijst naar een methode van de form-bean die verantwoordelijk is voor het retourneren van de navigatiesleutel. De methoden waarnaar in [Form.java] wordt verwezen, zijn de volgende:

// gebeurtenissen
  public String doAction2(){
    return "form2";
  }
  
  public String doAction4(){
    return "form4";
  }
  
  public String doAlea(){
    // een willekeurig getal tussen 1 en 3
    int i=1+(int)(3*Math.random());
    // de navigatiesleutel wordt weergegeven
    return "form"+i;
  }
  
  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

We laten de methode throwException op regel 17 voorlopig buiten beschouwing. Daar komen we later op terug. De methoden doAction2 en doAction4 geven alleen de navigatiesleutel terug zonder verdere verwerking. We hadden dus net zo goed het volgende kunnen schrijven:


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

De methode doAlea genereert daarentegen een willekeurige navigatiesleutel waarvan de waarde afkomstig is uit de verzameling {"form1", "form2", "form3"}.

De code van de pagina’s [form2.xhtml, form3.xhtml, form3.xhtml] is vergelijkbaar met die van de pagina [form1.xhtml].

2.7.3.2. Levensduur van het sjabloon [Form.java] van de pagina's [form*.xhtml]

Laten we de volgende reeks acties eens bekijken:

  • in [1] vullen we pagina 1 in en gaan we naar pagina 3,
  • in [2] vult men pagina 3 in en keert men terug naar pagina 1,
  • in [3], verschijnt pagina 1 weer zoals deze is ingevoerd. Vervolgens keert men terug naar pagina 3,
  • in [4], en pagina 3 wordt weergegeven zoals deze is ingevoerd.

Het mechanisme van het verborgen veld [javax.faces.ViewState] volstaat niet om dit fenomeen te verklaren.

Bij de overgang van [1] naar [2] vinden verschillende stappen plaats:

  • het sjabloon [Form.java] wordt bijgewerkt met de waarde van POST uit [form1.jsp]. Met name krijgt het veld inputText de waarde "een andere tekst",
  • de navigatiesleutel "form3" zorgt ervoor dat [form3.xhtml] wordt weergegeven. Het in [form3.xhtml] opgenomen ViewState geeft alleen de status weer van de componenten van [form3.xhtml], niet die van [form1.xhtml].

Bij de overgang van [2] naar [3]:

  • wordt het model [Form.java] bijgewerkt met het model POST uit [form3.xhtml]. Als de levensduur van het model [Form.java] is verstreken, wordt een geheel nieuw object [Form.java] aangemaakt, dat vervolgens wordt bijgewerkt met de waarde van POST uit [form3.xhtml]. In dat geval krijgt het veld inputText van het model zijn standaardwaarde terug:

  private String inputText="texte";

en behoudt deze: in het POST van [form3.xhtml] is er niets dat het veld inputText bijwerkt, dat deel uitmaakt van het sjabloon van [form1.xhtml] en niet van dat van [form3.xhtml],

  • de navigatiesleutel "form1" zorgt ervoor dat [form1.xhtml] wordt weergegeven. De pagina geeft het bijbehorende sjabloon weer. In ons geval zal het invoerveld login, dat gekoppeld is aan het sjabloon inputText, texte weergeven en niet de waarde „een andere tekst“ die in [1] is ingevoerd. Om ervoor te zorgen dat het veld inputText de waarde behoudt die is ingevoerd in [1], moet de levensduur van het sjabloon [Form.java] ‘sessie’ zijn en niet ‘verzoek’. In dat geval
    • zal het sjabloon na afloop van POST van [form1.xhtml] in de sessie van de klant worden geplaatst. Het veld inputText krijgt de waarde "een andere tekst",
    • op het moment van de POST van [form3.xhtml] wordt het sjabloon in deze sessie opgezocht en bijgewerkt door de POST van [form3.xhtml]. Het veld inputText wordt niet bijgewerkt door deze POST, maar behoudt de waarde "een andere tekst" die is verkregen na afloop van de POST vanuit [form1.xhtml] en [1].

De declaratie van de bean [Form.java] luidt dus als volgt:


package forms;

import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;

@ManagedBean
@SessionScoped
public class Form {

Regel 8 geeft de bean een sessiebereik.

2.7.4. Uitzonderingsafhandeling

Laten we nog eens terugkomen op de algemene architectuur van een JSF-toepassing:

Wat gebeurt er wanneer een gebeurtenishandler of een model een uitzondering opvangt uit de bedrijfslaag, bijvoorbeeld een onverwachte verbroken verbinding met een database?

  • De gebeurtenishandlers [2a] kunnen elke uitzondering onderscheppen die vanuit de laag [métier] wordt doorgegeven en aan de controller [Faces Servlet] een navigatiesleutel doorgeven naar een foutpagina die specifiek is voor de uitzondering,
  • Voor de modellen is deze oplossing niet bruikbaar, omdat wanneer ze worden aangeroepen ([3,4]), men zich in de weergavefase van een specifieke pagina (XHTML) bevindt en niet langer in de fase waarin die pagina wordt gekozen. Hoe kun je van pagina wisselen terwijl je je in de weergavefase van een van die pagina's bevindt? Een eenvoudige oplossing, die echter niet altijd geschikt is, bestaat erin de uitzondering niet af te handelen, waardoor deze wordt doorgegeven aan de servletcontainer die de applicatie uitvoert. Deze kan zo worden geconfigureerd dat er een specifieke pagina wordt weergegeven wanneer een uitzondering wordt doorgegeven aan de servletcontainer. Deze oplossing is altijd bruikbaar en we zullen deze nu nader bekijken.

2.7.4.1. Configuratie van de webapplicatie voor uitzonderingsafhandeling

De configuratie van een webapplicatie voor uitzonderingsafhandeling vindt plaats in het bestand [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>

Op de regels 32-39 staan de definities van twee foutpagina’s. Er kunnen zoveel <error-page>-tags worden gebruikt als nodig is. De <location>-tag geeft aan welke pagina bij een fout moet worden weergegeven. Het type fout dat aan de pagina is gekoppeld, kan op twee manieren worden gedefinieerd:

  • via de tag <exception-type>, die het Java-type van de afgehandelde uitzondering definieert. Zo geeft de tag <error-page> in de regels 36-39 aan dat als de servletcontainer tijdens de uitvoering van de applicatie een uitzondering van het type [java.lang.Exception] of een afgeleid type (regel 37) opvangt, de pagina [/faces/exception.xhtml] (regel 38). Door hier het meest generieke uitzonderingstype [java.lang.Exception] te gebruiken, zorgen we ervoor dat alle uitzonderingen worden afgehandeld,
  • via de tag <error-code> (regel 33) die een foutcode HTTP definieert. Als een browser bijvoorbeeld URL [http://machine:port/contexte/P] opvraagt en pagina P niet bestaat in de applicatiecontext, grijpt de applicatie niet in bij het antwoord. Het is de servletcontainer die dit antwoord genereert door een standaardfoutpagina te verzenden. De eerste regel van de HTTP-stream in dit antwoord bevat een foutcode 404, wat aangeeft dat de opgevraagde pagina P niet bestaat. Het kan wenselijk zijn om een antwoord te genereren dat bijvoorbeeld voldoet aan de huisstijl van de applicatie of dat links bevat om het probleem op te lossen. In dat geval gebruikt men een <error-page>-tag met een <error-code>404</error-code>-tag.

Hierboven is de foutcode HTTP (fout 500) de code die wordt teruggestuurd wanneer de applicatie „crasht“. Dit is de code die zou worden teruggestuurd als er een uitzondering zou worden doorgegeven aan de servletcontainer. De twee <error-page>-tags in de regels 28-35 zijn dus waarschijnlijk overbodig. We hebben ze allebei geplaatst om de twee manieren om een fout af te handelen te illustreren.

2.7.4.2. Het simuleren van de uitzondering

Er wordt kunstmatig een uitzondering gegenereerd via de link [Lancer une exception]:

Een klik op de link [Lancer une exception] [1] zorgt ervoor dat de pagina [2] wordt weergegeven.

In de code van de pagina's [formx.xhtml] wordt de link [Lancer une exception] als volgt gegenereerd:


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

Op regel 5 is te zien dat bij het klikken op de link de methode [form].throwException wordt uitgevoerd. Deze luidt als volgt:


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

Hierin wordt een uitzondering van het type [java.lang.Exception] gegenereerd. Deze wordt doorgegeven aan de servletcontainer, die vervolgens de pagina [/faces/exception.xhtml] weergeeft.

2.7.4.3. Informatie over een uitzondering

Wanneer een uitzondering wordt doorgegeven aan de servletcontainer, zal deze de bijbehorende foutpagina weergeven door informatie over de uitzondering door te geven aan die pagina. Deze informatie wordt toegevoegd als nieuwe attributen van het verzoek dat op dat moment wordt verwerkt. Het verzoek van een browser en het antwoord dat deze ontvangt, worden ingekapseld in Java-objecten van het type [HttpServletRequest request] en [HttpServletResponse response]. Deze objecten zijn beschikbaar in alle fasen van de verwerking van het browserverzoek.

Bij ontvangst van het verzoek HTTP van de browser verpakt de servletcontainer dit in het Java-object [HttpServletRequest request] en maakt het object [HttpServletResponse response] aan, waarmee het antwoord kan worden gegenereerd. In dit object bevindt zich met name het kanaal TCP-IP dat gebruikt moet worden voor de stroom HTTP van het antwoord. Alle lagen t1, t2, ..., tn die betrokken zijn bij de verwerking van het object request, hebben toegang tot deze twee objecten. Elke laag kan toegang hebben tot de elementen van het oorspronkelijke verzoek request en het antwoord voorbereiden door het object response aan te vullen. Een laag van localisation kan bijvoorbeeld de localisation van het antwoord vastleggen via de methode response.setLocale(Locale l).

De verschillende ti-lagen kunnen informatie doorgeven via het object request. Dit object beschikt over een attribuutwoordenboek, dat bij het aanmaken leeg is en door de opeenvolgende verwerkingslagen kan worden aangevuld. Deze kunnen in de attributen van het object request de informatie plaatsen die nodig is voor de volgende verwerkingslaag. Er zijn twee methoden om de attributen van het object request te beheren:

  • void setAttribute(String s, Object o), waarmee aan de attributen een object o kan worden toegevoegd dat wordt geïdentificeerd door de tekenreeks s,
  • Object getAttribute(String s), waarmee het attribuut o, geïdentificeerd door de tekenreeks s, kan worden opgehaald.

Wanneer een uitzondering wordt doorgegeven aan de servletcontainer, voegt deze de volgende attributen toe aan de verzoek dat op dat moment wordt verwerkt:

sleutel
waarde
javax.servlet.error.status_code
de foutcode HTTP die naar de klant wordt teruggestuurd
javax.servlet.error.exception
het Java-type van de uitzondering, vergezeld van de foutmelding.
javax.servlet.error.request_uri
de URL die werd aangeroepen toen de uitzondering zich voordeed
javax.servlet.error.servlet_name
de servlet die het verzoek verwerkte toen de uitzondering zich voordeed

We zullen deze attributen van het verzoek gebruiken op de pagina [exception.xhtml] om ze weer te geven.

2.7.4.4. De foutpagina [exception.xhtml]

De inhoud ervan is als volgt:


<?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. De uitdrukkingen op de uitzonderingspagina

In de verwerkingsketen van de verzoek van de klant is de pagina XHTML normaal gesproken de laatste schakel in de keten:

Alle elementen in de keten zijn Java-klassen, inclusief de pagina XHTML. Deze wordt namelijk door de servletcontainer omgezet in een servlet, c.a.d, en vervolgens in een gewone Java-klasse. Om precies te zijn wordt de pagina XHTML omgezet in Java-code die wordt uitgevoerd binnen de volgende methode:


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

Vanaf regel 14 staat de Java-code die overeenkomt met de pagina XHTML. Deze code beschikt over een aantal objecten die zijn geïnitialiseerd door de methode _jspService, regel 1 hierboven:

  • regel 1: HttpServletRequest request: het verzoek dat momenteel wordt verwerkt,
  • regel 1: HttpServletResponse response: het antwoord dat naar de klant wordt verzonden,
  • regel 7: ServletContext application: een object dat de webapplicatie zelf vertegenwoordigt. Net als het object request kan het object application attributen hebben. Deze worden gedeeld door alle verzoeken van alle klanten. Het zijn doorgaans alleen-lezen-attributen,
  • regel 6: HTTPSession sessie: vertegenwoordigt de sessie van de klant. Net als de objecten request en application kan het object session attributen hebben. Deze worden gedeeld door alle verzoeken van dezelfde klant,
  • regel 9: JspWriter out: een schrijfstroom naar de browser van de klant. Dit object is nuttig voor het debuggen van een pagina XHTML. Alles wat via out.println(tekst) wordt geschreven, wordt weergegeven in de browser van de klant.

Wanneer in de pagina JSF #{uitdrukking} wordt geschreven, kan uitdrukking de sleutel zijn van een attribuut van de bovenstaande objecten request, session of application. Het bijbehorende attribuut wordt achtereenvolgens in deze drie objecten gezocht. Zo wordt #{sleutel} als volgt geëvalueerd:

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

Zodra een waarde wordt gevonden die niet null is, wordt de evaluatie van #{sleutel} gestopt. Men kan nauwkeuriger te werk gaan door de context aan te geven waarin het attribuut moet worden gezocht:

  • #{requestScope['clé']} om het attribuut in het request-object te zoeken,
  • #{sessionScope['clé']} om het attribuut in het session-object te zoeken,
  • #{applicationScope['clé']} om het attribuut in het object ‘application’ te zoeken.

Dit is gedaan op pagina [exception.xhtml], pagina 116. De volgende attributen worden gebruikt:

sleutel
domein
waarde
javax.servlet.error.status_code
verzoek
zie paragraaf 2.7.4.3.
javax.servlet.error.exception
idem
idem
javax.servlet.error.request_uri
idem
idem
javax.servlet.error.servlet_name
idem
idem

De verschillende berichten die nodig zijn voor de pagina JSF [exception.xhtml] zijn toegevoegd aan de reeds bestaande berichtenbestanden:

[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. Voorbeeld mv-jsf2-06: validatie en conversie van invoer

2.8.1. De applicatie

De applicatie toont een invoerformulier. Bij validatie van dit formulier wordt hetzelfde formulier als antwoord teruggestuurd, eventueel met foutmeldingen als de invoer onjuist is bevonden.

2.8.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

Het project [mv-jsf2-06] is opnieuw gebaseerd op één enkele pagina [index.html] [1] en het bijbehorende sjabloon [Form.java] [2]. Het maakt nog steeds gebruik van berichten uit [messages.properties], maar uitsluitend in het Frans ([3]). De optie om van taal te wisselen is niet beschikbaar.

2.8.3. De omgeving van de applicatie

Hier geven we de inhoud weer van de bestanden waarmee de applicatie wordt geconfigureerd, zonder specifieke uitleg te geven. Deze bestanden helpen om het volgende beter te begrijpen.

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

Regel 17 is nieuw. Deze wordt later toegelicht.

Het berichtenbestand [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="!"

Het stylesheet [styles.css] ziet er als volgt uit:


.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. De pagina [index.xhtml] en het bijbehorende sjabloon [Form.java]

De pagina [index.xhtml] is als volgt:


<?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">
        <!-- regel 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"/>
        <!-- regel 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}"/>
        <!-- regel 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}"/>
        <!-- regel 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}"/>
        <!-- regel 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}"/>
        <!-- regel 6 -->
        ...
        <!-- regel 7 -->
        ...
        <!-- regel 8 -->
        ...
        <!-- regel 9 -->
        ...
        <!-- regel 10 -->
        ...
        <!-- regel 11 -->
        ...
        <!-- regel 12 -->
        ...
        <!-- regel 13 -->
        ...
      </h:panelGrid>
      <!-- bedieningsknoppen -->
      <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>

De belangrijkste nieuwigheid is de aanwezigheid van tags:

  • om foutmeldingen weer te geven <h:messages> (regel 14), <h:message> (regels 24, 29, 34),
  • die geldigheidsvoorwaarden opleggen aan invoer <f:validateLongRange> (regel 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
  • die een converter definiëren tussen de invoer en het bijbehorende model, zoals <f:convertDateTime>.

Het sjabloon van deze pagina is de volgende 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() {
}
// invoervelden
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 = "";

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

public String cancel() {
...
}

// validatoren
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// getters en setters
...
}

Het nieuwe hier is dat de velden van het sjabloon niet langer uitsluitend van het type String zijn, maar van verschillende typen.

2.8.5. De verschillende invoervelden van het formulier

We bekijken nu achtereenvolgens de verschillende invoervelden van het formulier.

2.8.5.1. Invoervelden 1 tot en met 4: invoer van een geheel getal

Op de pagina [index.xhtml] wordt invoerveld 1 als volgt weergegeven:


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

Het sjabloon form.saisie1 is als volgt gedefinieerd in [Form.java]:


private Integer saisie1 = 0;

In de browser, op een GET, levert de pagina [index.xhtml], die gekoppeld is aan het bijbehorende sjabloon [Form.java], visueel het volgende resultaat op:

  • regel 2 levert [1] op,
  • regel 3 levert [2] op,
  • regel 4 levert [3] op,
  • regel 5 levert [4] op.

Stel dat de volgende invoer wordt gedaan en vervolgens wordt bevestigd:

Dan krijgen we het volgende resultaat in het formulier dat door de applicatie wordt teruggestuurd:

  • in [1], de foutieve invoer,
  • in [2], de foutmelding die dit aangeeft,
  • in [3] zien we dat de waarde van het veld Integer saisie1 van het model niet is gewijzigd.

Laten we eens uitleggen wat er is gebeurd. Laten we daarvoor teruggaan naar de verwerkingscyclus van een pagina JSF:

We bekijken deze cyclus voor de component:


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

en het bijbehorende sjabloon:


private Integer saisie1 = 0;
  • in [A] wordt de pagina [index.xhtml], die tijdens de GET van de browser is verzonden, hersteld. In [A] is de pagina precies zoals de gebruiker deze heeft ontvangen. De component id="saisie1" krijgt zijn oorspronkelijke waarde "0" terug,
  • in [B] krijgen de componenten van de pagina de waarden die door de browser zijn verzonden. In [B] is de pagina zoals de gebruiker deze heeft ingevoerd en bevestigd. De component id="saisie1" krijgt de verzonden waarde „x“ toegewezen,
  • in [C] worden, indien de pagina expliciete validatoren en converters bevat, deze uitgevoerd. Er worden ook impliciete converters uitgevoerd als het type van het veld dat aan de component is gekoppeld niet van het type String is. Dit is hier het geval, waarbij het veld form.saisie1 van het type Integer is. JSF zal proberen de waarde "x" van de component id="saisie1" om te zetten naar het type Integer. Dit zal een fout veroorzaken die de verwerkingscyclus [A-F] zal stoppen. Deze fout wordt gekoppeld aan de component id="saisie1". Via [D2] gaat men vervolgens direct door naar de weergavefase van het antwoord. Dezelfde pagina [index.xhtml] wordt teruggestuurd,
  • de fase [D] vindt alleen plaats als alle componenten van een pagina de conversie-/validatiefase hebben doorlopen. In deze fase wordt de waarde van de component id="saisie1" toegewezen aan het bijbehorende model form.saisie1.

Als de fase [C] mislukt, wordt de pagina opnieuw weergegeven en wordt de volgende code opnieuw uitgevoerd:


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

Het bericht dat wordt weergegeven in [2] is afkomstig uit regel 4 van [index.xhtml]. De tag <h:message for="idComposant"/> geeft de foutmelding weer die hoort bij de component die wordt aangeduid door het for-attribuut, indien er een fout is. Het bericht dat wordt weergegeven in [2] is standaard en bevindt zich in het bestand [javax/faces/Messages.properties] van het archief [jsf-api.jar]:

In [2] zien we dat het berichtbestand in verschillende varianten bestaat. Laten we de inhoud van [Messages_fr.properties] eens bekijken:

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

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


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

Het bestand bevat berichten die zijn onderverdeeld in categorieën:

  • fouten in een component, regel 3,
  • conversiefouten tussen een component en het bijbehorende model, regel 12
  • validatiefouten wanneer er validatoren op de pagina aanwezig zijn, regel 23.

De fout die is opgetreden bij de component id="saisie1" is een conversiefout van het type String naar het type Integer. De bijbehorende foutmelding staat op regel 18 van het berichtenbestand.

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

De weergegeven foutmelding wordt hieronder weergegeven:

In de foutmelding is te zien dat:

  • de parameter {2} is vervangen door de ID van de component waarvoor de conversiefout is opgetreden,
  • de parameter {0} is vervangen door de invoer die in [1] voor de component is gedaan,
  • de parameter {1} is vervangen door het getal 9346.

De meeste berichten met betrekking tot componenten hebben twee versies: een beknopte versie (summary) en een gedetailleerde versie (detail). Dit is het geval bij de regels 16-18:

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}

Het bericht met de sleutel _detail (regel 2) is het zogenaamde gedetailleerde bericht. Het andere is het zogenaamde beknopte bericht. De tag <h:message> geeft standaard het gedetailleerde bericht weer. Dit gedrag kan worden gewijzigd met de attributen showSummary en showDetail. Dit is wat er is gedaan voor de component met id saisie2:


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

Op regel 2 is de component saisie2 gekoppeld aan het volgende veld form.saisie2:


  private Integer saisie2 = 0;

Het verkregen resultaat is als volgt:

  • in [1] staat het gedetailleerde bericht, in [2] het beknopte bericht.

De tag <h:messages> geeft in de vorm van een lijst alle samengevatte foutmeldingen van alle componenten weer, evenals foutmeldingen die niet aan een component zijn gekoppeld. Ook hier kunnen attributen dit standaardgedrag wijzigen:

  • showDetail: true / false om al dan niet gedetailleerde meldingen op te vragen,
  • showSummary: true / false om al dan niet de samengevatte meldingen weer te geven,
  • globalOnly: true / false om al dan niet alleen foutmeldingen weer te geven die niet aan componenten zijn gekoppeld. Zo’n foutmelding zou bijvoorbeeld door de ontwikkelaar kunnen zijn aangemaakt.

De foutmelding die bij een conversie hoort, kan op verschillende manieren worden gewijzigd. Allereerst kan de applicatie worden geïnstrueerd om een ander berichtenbestand te gebruiken. Deze wijziging wordt aangebracht 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>

De regels 3-8 definiëren een berichtenbestand, maar dit is niet het bestand dat wordt gebruikt door de tags <h:message> en <h:messages>. Om dit te definiëren, moet de tag <message-bundle> op regel 9 worden gebruikt. Regel 9 geeft aan de tags <h:message(s)> door dat het bestand [messages.properties] vóór het bestand [javax.faces.Messages.properties] moet worden doorzocht. Als we dus de volgende regels toevoegen aan het bestand [messages_fr.properties]:


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

wordt de foutmelding voor de componenten saisie1 en saisie2:

Image

Een andere manier om de conversiefoutmelding aan te passen, is door het attribuut converterMessage van de component te gebruiken, zoals hieronder weergegeven voor de component saisie3:


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

De component saisie3 is gekoppeld aan het volgende veld form.saisie3:


  private Integer saisie3 = 0;
  • regel 3: het attribuut converterMessage bepaalt expliciet de melding die bij een conversiefout moet worden weergegeven;
  • regel 3: het attribuut required="true" geeft aan dat het invullen verplicht is. Het veld mag niet leeg blijven. Een veld wordt als leeg beschouwd als het geen tekens bevat of als het een reeks spaties bevat. Ook hier is er in [javax.faces.Messages.properties] een standaardmelding:
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.

Met het attribuut requiredMessage kun je deze standaardmelding vervangen. Als het bestand [messages.properties] de volgende meldingen bevat:


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

dan kan het volgende resultaat worden verkregen:

of dit:

Controleren of een invoer inderdaad een geheel getal is, volstaat niet altijd. Soms moet worden gecontroleerd of het ingevoerde getal binnen een bepaald interval valt. In dat geval wordt een validator gebruikt. Invoer nr. 4 is hier een voorbeeld van. De code ervan in [index.xhtml] is als volgt:


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

Regel 3: de component saisie4 is gekoppeld aan het volgende model form.saisie4:


  private Integer saisie4 = 0;

Regels 3-5: de tag <h:inputText> heeft een onderliggende tag <f:validateLongRange> die twee optionele attributen heeft: minimum en maximum. Met deze tag, ook wel validator genoemd, kan een beperking worden toegevoegd aan de waarde van de invoer: deze moet niet alleen een geheel getal zijn, maar ook een geheel getal binnen het interval [minimum, maximum] als de twee attributen minimum en maximum aanwezig zijn, groter dan of gelijk aan minimum als alleen het attribuut minimum aanwezig is, en kleiner dan of gelijk aan maximum als alleen het attribuut maximum aanwezig is. De validator <f:validateLongRange> heeft standaardfoutmeldingen 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.

Ook hier is het mogelijk om deze meldingen te vervangen door andere. Er bestaat een attribuut validatorMessage waarmee een specifieke melding voor de component kan worden gedefinieerd. Zo kan met de volgende 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>

en de volgende melding in [messages.properties]:


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

krijgt men het volgende resultaat:

Image

2.8.5.2. Invoer 5 en 6: invoer van een reëel getal

Voor het invoeren van reële getallen gelden vergelijkbare regels als voor het invoeren van gehele getallen. De code XHTML voor invoer 5 en 6 is als volgt:


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

De elementen van het sjabloon [Form.java] die verband houden met de componenten saisie5 en saisie6:


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

De foutmeldingen die verband houden met de converters en validators van de componenten saisie5 en saisie6, in [messages.properties]:


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

Hier volgt een voorbeeld van een uitvoering:

Image

2.8.5.3. Invoer 7: invoer van een booleaanse waarde

Het invoeren van een booleaanse waarde zou normaal gesproken moeten gebeuren met een selectievakje. Als dit gebeurt met een invoerveld, wordt de tekenreeks "true" omgezet in de booleaanse waarde true en elke andere tekenreeks in de booleaanse waarde false.

De code XHTML uit het voorbeeld:


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

Het sjabloon van de component saisie7:


  private Boolean saisie7 = true;

Hier volgt een voorbeeld van een invoer en de bijbehorende reactie:

In [1] staat de ingevoerde waarde. Door conversie wordt deze tekenreeks "x" omgezet naar de booleaanse waarde false. Dit wordt weergegeven door [2]. De waarde [3] van het model is niet veranderd. Deze verandert pas wanneer alle conversies en validaties op de pagina zijn geslaagd. Dat was in dit voorbeeld niet het geval.

2.8.5.4. Invoer 8: invoer van een datum

Het invoeren van een datum gebeurt in het voorbeeld met de volgende code XHTML:


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

De component saisie8 in regel 3 maakt gebruik van een converter java.lang.String <--> java.util.Date. Het model form.saisie8 dat aan de component saisie8 is gekoppeld, is als volgt:


  private Date saisie8 = new Date();

De component die wordt gedefinieerd door de regels 7-9 maakt ook gebruik van een converter, maar uitsluitend in de richting java.util.Date --> java.lang.String.

De converter <f:convertDateTime> ondersteunt diverse attributen, waaronder het attribuut **pattern,** dat de vorm bepaalt van de tekenreeks die in een datum moet worden omgezet of waarmee een datum moet worden weergegeven.

Bij de eerste aanvraag van de pagina [index.xhtml] wordt de voorgaande regel 8 als volgt weergegeven:

De velden [1] en [2] geven beide de waarde van het sjabloon form.saisie8 weer:


  private Date saisie8 = new Date();

waarbij saisie8 de waarde van de huidige datum aanneemt. De converter die in beide gevallen wordt gebruikt voor de weergave van de datum is de volgende:


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

waarbij dd (day) het dagnummer aangeeft, MM (Month) het maandnummer en yyyy (year) het jaar. In [1] wordt de converter gebruikt voor de omgekeerde conversie java.lang.String --> java.util.Date. De ingevoerde datum moet dus het formaat "dd/MM/yyyy" volgen om geldig te zijn.

Er zijn standaardmeldingen voor ongeldige datums in [javax.faces.Messages.properties]:

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 je kunt vervangen door je eigen meldingen. Zoals in het voorbeeld:


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

zal bij een conversiefout de volgende sleutelmelding saisie8.error worden weergegeven:


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

Hier volgt een voorbeeld:

Image

2.8.5.5. Invoer 9: invoer van een tekenreeks met een beperkte lengte

Invoer 9 laat zien hoe je kunt opleggen dat een ingevoerde tekenreeks een aantal tekens moet hebben dat binnen een bepaald bereik valt:


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

Regel 4: de validator <f:validateLength minimum="4" maximum="4"/> schrijft voor dat de ingevoerde tekenreeks precies 4 tekens moet bevatten. Er kan slechts één van de attributen worden gebruikt: minimum voor een minimumaantal tekens, maximum voor een maximumaantal.

Het model form.saisie9 van de component saisie9 uit regel 3 is als volgt:


  private String saisie9 = "";

Er zijn standaardfoutmeldingen voor dit type validatie:

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 kunnen worden vervangen door het attribuut validatorMessage te gebruiken, zoals in regel 3 hierboven. De sleutelmelding saisie9.error is als volgt:


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

Hier volgt een uitvoervoorbeeld:

Image

2.8.5.6. Invoer 9B: invoer van een tekenreeks die aan een sjabloon moet voldoen

De invoer 9B laat zien hoe je kunt opleggen dat een ingevoerde tekenreeks een aantal tekens binnen een bepaald bereik moet hebben:


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

Op regel 4 dwingt de validator <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> de ingevoerde tekenreeks om te voldoen aan het patroon van een reguliere expressie, in dit geval: een reeks van 0 of meer spaties, 2 cijfers, het teken :, 2 cijfers, een reeks van 0 of meer spaties.

Het patroon form.saisie9B van de component saisie9B in regel 3 is als volgt:


private String saisie9B;

Er zijn standaardfoutmeldingen voor dit type validatie:

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 kunnen worden vervangen met behulp van het attribuut validatorMessage, zoals in regel 3 hierboven. De sleutelmelding saisie9.error is als volgt:


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

Hier volgt een uitvoervoorbeeld:

Image

2.8.5.7. Invoer 10: een specifieke validatiemethode schrijven

Samenvattend: met JSF kunt u binnen de ingevoerde waarden de geldigheid van getallen (gehele getallen, reële getallen), datums, de lengte van tekenreeksen en de conformiteit van een invoer ten opzichte van een reguliere expressie controleren. Met JSF kunt u uw eigen validatoren en converters toevoegen aan de bestaande validatoren en converters. Dit punt wordt hier niet behandeld, maar u kunt [ref2] raadplegen voor meer informatie.

We presenteren hier een andere methode: het valideren van ingevoerde gegevens via een methode van het formuliermodel. Dit is het volgende voorbeeld:


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

Het model form.saisie10 dat is gekoppeld aan de component saisie10 in regel 3 ziet er als volgt uit:


  private Integer saisie10 = 0;

We willen dat het ingevoerde getal <1 of >7 is. Dit kan niet worden gecontroleerd met de standaardvalidatoren van JSF. Daarom schrijven we een eigen validatiemethode voor de component saisie10. Dit geven we aan met het attribuut validator van de te valideren component:


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

De component saisie10 wordt gevalideerd door de methode form.validateSaisie10. Deze ziet er als volgt uit:


  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);
    }
}

De handtekening van een validatiemethode moet overeenkomen met die van regel 1:

  • FacesContext context: uitvoeringscontext van de pagina – geeft toegang tot diverse informatie, met name tot de objecten HttpServletRequest request en HttpServletResponse response,
  • UIComponent component: de component die moet worden gevalideerd. De tag <h:inputText> wordt vertegenwoordigd door een component van het type UIInput, afgeleid van UIComponent. Hier is het deze component UIInput die als tweede parameter wordt ontvangen,
  • Objectwaarde: de ingevoerde waarde die moet worden gecontroleerd, omgezet naar het type van het model. Het is belangrijk om te begrijpen dat als de conversie van String naar het type van het model is mislukt, de validatiemethode niet wordt uitgevoerd. Wanneer de methode validateSaisie10 wordt aangeroepen, betekent dit dat de conversie van String naar Integer is geslaagd. De derde parameter is dan van het type Integer.
  • regel 2: de ingevoerde waarde wordt omgezet naar het type int,
  • regel 3: er wordt gecontroleerd of de ingevoerde waarde <1 of >7 is. Als dat het geval is, is de validatie voltooid. Als dat niet het geval is, moet de validator de fout melden door een uitzondering van het type ValidatorException te genereren.

De klasse ValidatorException heeft twee constructors:

  • de constructor [1] heeft als parameter een foutmelding van het type FacesMessage. Dit type melding wordt weergegeven door de tags <h:messages> en <h:message>,
  • met de constructor [2] kan bovendien de oorzaak van het type Throwable of een afgeleide van de fout worden ingekapseld.

We moeten een bericht van het type FacesMessage samenstellen. Deze klasse heeft verschillende constructors:

De constructor [1] definieert de eigenschappen van een object van het type FacesMessage:

  • FacesMessage.Severity severity: een ernstniveau uit de volgende opsomming: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
  • String summary: de beknopte versie van het foutbericht – wordt weergegeven door de tags <h:message showSummary="true"> en <h:messages>,
  • String detail: de gedetailleerde versie van de foutmelding – wordt weergegeven door de tags <h:message> en <h:messages showDetail="true">.

Elke van de constructors kan worden gebruikt; ontbrekende parameters kunnen later worden ingesteld via de methoden set.

Met de constructor [1] is het niet mogelijk om een bericht aan te wijzen dat zich in een geïnternationaliseerd berichtenbestand bevindt. Dat is natuurlijk jammer. David Geary en Cay Horstmann vullen deze leemte in hun boek „Core JavaServer Faces” aan met de hulpprogrammaklasse com.corejsf.util.Messages. Deze klasse wordt in regel 4 van de Java-code gebruikt om de foutmelding te genereren. Ze bevat uitsluitend statische methoden, waaronder de methode getMessage die in regel 4 wordt gebruikt:


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

De methode getMessage accepteert drie parameters:

  • String bundleName: de naam van een berichtenbestand zonder de extensie .properties, maar met de pakketnaam. Hier zou onze eerste parameter messages kunnen zijn om het bestand [messages.properties] aan te duiden. Voordat het bestand wordt gebruikt dat door de eerste parameter wordt aangeduid, probeert getMessage het berichtenbestand van de applicatie te gebruiken, indien er een is. Als er dus in [faces-config.xml] een berichtenbestand is gedeclareerd met de tag:

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

dan kan null als eerste parameter worden doorgegeven aan de methode getMessage. Dit is hier gedaan (zie [web.xm], pagina 120),

  • String resourceId: de sleutel van het te verwerken bericht in het berichtenbestand. We hebben gezien dat een bericht zowel een beknopte als een gedetailleerde versie kan hebben. resourceId is de identificatiecode van de beknopte versie. De gedetailleerde versie wordt automatisch opgezocht met de sleutel resourceId_detail. Zo hebben we twee berichten in [messages.properties] voor de fout bij invoer nr. 10:

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

Het bericht van het type FacesMessage, gegenereerd door de methode Messages.getMessage, bevat zowel de beknopte als de gedetailleerde versie, indien deze zijn gevonden. Beide versies moeten aanwezig zijn; anders wordt er een uitzondering van het type [NullPointerException] gegenereerd,

  • Object[] params: de daadwerkelijke parameters van het bericht als dit formele parameters {0}, {1}, ... heeft. Deze formele parameters worden vervangen door de elementen van de array params.

Laten we teruggaan naar de code van de validatiemethode van de component saisie10:


  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] wordt het bericht van het type FacesMessage aangemaakt met behulp van de statische methode Messages.getMessage,
  • in [5] wordt de ernstgraad van het bericht ingesteld,
  • in [6] wordt een uitzondering van het type ValidatorException gegenereerd met het eerder opgebouwde bericht. De validatiemethode is aangeroepen door de volgende code XHTML:

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

Op regel 3 wordt de validatiemethode uitgevoerd voor de component met id saisie10. Het foutbericht dat door de methode validateSaisie10 wordt gegenereerd, wordt dus aan deze component gekoppeld en daarom weergegeven op regel 4 (attribuut for="saisie10"). De gedetailleerde versie wordt standaard weergegeven door de tag <h:message>.

Hier volgt een voorbeeld van de uitvoering:

Image

2.8.5.8. Invoer 11 en 12: validatie van een groep componenten

Tot nu toe valideerden de besproken validatiemethoden slechts één component. Hoe gaat u te werk als de gewenste validatie betrekking heeft op meerdere componenten? Dat gaan we nu bekijken. In het formulier:

Image

willen we dat de invoervelden 11 en 12 twee gehele getallen zijn waarvan de som gelijk is aan 10.

De code JSF ziet er als volgt uit:


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

en het bijbehorende sjabloon:


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

In regel 3 van de code JSF gebruiken we de eerder beschreven technieken om te controleren of de ingevoerde waarde voor de component saisie11 inderdaad een geheel getal is. Hetzelfde geldt, in regel 11, voor de component saisie12. Om te controleren of saisie11 + saisie12 = 10 is, zouden we een specifieke validator kunnen bouwen. Dat is de voorkeursoplossing. Ook hier zullen we [ref2] raadplegen om deze te ontdekken. We volgen hier echter een andere aanpak.

De pagina [index.xhtml] wordt gevalideerd door een knop [Valider], waarvan de code JSF als volgt luidt:


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

waarbij het bericht msg['submit'] als volgt luidt:


submit=Valider

Op regel 3 zien we dat de methode form.submit wordt uitgevoerd om de klik op de knop [Valider] te verwerken. Deze luidt als volgt:


  // acties
  public String submit() {
    // laatste validaties
    validateForm();
    // hetzelfde formulier wordt teruggestuurd
    return null;
  }

  // algemene validaties
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
...
}

Het is belangrijk om te begrijpen dat wanneer de methode **submit** wordt uitgevoerd:

  • alle validatoren en converters van het formulier zijn uitgevoerd en geslaagd,
  • de velden van het model [Form.java] de door de klant verzonden waarden hebben ontvangen.

Laten we namelijk even terugkeren naar de verwerkingscyclus van een POST JSF:

De methode submit is een gebeurtenishandler. Deze verwerkt de gebeurtenis clic op de knop [Valider]. Net als alle gebeurtenishandlers wordt deze uitgevoerd in de fase [E], nadat alle validatoren en converters zijn uitgevoerd en geslaagd zijn ([C]) en het model is bijgewerkt met de verzonden waarden ([D]). Het gaat hier dus niet meer om het genereren van uitzonderingen van het type [ValidatorException], zoals we eerder hebben gedaan. We beperken ons ertoe het formulier terug te sturen met foutmeldingen:

In [1] waarschuwen we de gebruiker en in [2] en [3] plaatsen we een foutmarkering. In de code JSF wordt de melding [1] als volgt verkregen:


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

Op regel 2 geeft de tag <h:messages> standaard de beknopte versie weer van de foutmeldingen van alle onjuiste invoer in formuliercomponenten, evenals alle foutmeldingen die geen verband houden met componenten. Het attribuut globalOnly="true" beperkt de weergave tot deze laatste.

De berichten [2] en [3] worden weergegeven met eenvoudige <h:outputText>-tags:


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

Regels 4-7: de component saisie11 kan twee foutmeldingen geven:

  • het bericht dat wijst op een foutieve conversie of ontbrekende gegevens. Dit bericht, dat door JSF zelf wordt gegenereerd, zal worden opgenomen in een type FacesMessage en weergegeven via de tag <h:message> op regel 5,
  • die we gaan genereren als invoer11 + invoer12 niet gelijk is aan 10. Deze wordt weergegeven via regel 6. De foutmelding wordt opgenomen in het sjabloon form.errorSaisie11.

De twee berichten hebben betrekking op fouten die niet tegelijkertijd kunnen optreden. De controle of invoer11 + invoer12 = 10 wordt uitgevoerd in de methode submit, die alleen wordt uitgevoerd als er geen fouten meer in het formulier staan. Wanneer deze wordt uitgevoerd, is de component saisie11 al gecontroleerd en heeft het bijbehorende sjabloon form.saise11 zijn waarde gekregen. Het bericht op regel 5 kan dan niet meer worden weergegeven. Omgekeerd geldt dat, als het bericht op regel 5 wordt weergegeven, er nog ten minste één fout in het formulier zit en de methode submit niet wordt uitgevoerd. Het bericht op regel 6 wordt niet weergegeven. Om ervoor te zorgen dat de twee mogelijke foutmeldingen in dezelfde kolom van de tabel staan, zijn ze samengevoegd in een <h:panelGroup>-tag (regels 4 en 7).

De methode submit is als volgt:


  // acties
  public String submit() {
    // laatste validaties
    validateForm();
    // hetzelfde formulier wordt teruggestuurd
    return null;
  }

  // algemene validaties
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
      // algemene melding
      FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      FacesContext context = FacesContext.getCurrentInstance();
      context.addMessage(null, message);
      // veldgerelateerde meldingen
      message = Messages.getMessage(null, "error.sign", null);
      setErrorSaisie11(message.getSummary());
      setErrorSaisie12(message.getSummary());
    } else {
      setErrorSaisie11("");
      setErrorSaisie12("");
    }
}
  • regel 4: de methode submit roept de methode validateForm aan om de laatste validaties uit te voeren,
  • regel 11: er wordt gecontroleerd of saisie11+saisie12=10,
  • als dat niet het geval is, regels 13-14, wordt een bericht van het type FacesMessage aangemaakt met het bericht-id saisies11et12.incorrectes. Dit is als volgt:

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
  • het aldus opgebouwde bericht wordt (regels 15-16) toegevoegd aan de lijst met foutberichten van de applicatie. Dit bericht is niet gekoppeld aan een specifieke component. Het is een algemeen bericht van de applicatie. Het wordt weergegeven door de hierboven genoemde tag <h:messages globalOnly="true"/>,
  • regel 18: er wordt een nieuwe melding van het type FacesMessage aangemaakt met de melding met id error.sign. Deze luidt als volgt:

error.sign="!"

We hebben gezegd dat de statische methode [Messages.getMessage] een bericht van het type FacesMessage samenstelt met een beknopte versie en een gedetailleerde versie, indien deze bestaan. Hier bestaat alleen de beknopte versie van het bericht error.sign. De beknopte versie van een bericht m wordt verkregen via m.getSummary(). Regels 19 en 20: de beknopte versie van het bericht error.sign wordt in de velden errorSaisie11 en errorSaisie12 van het sjabloon geplaatst. Deze worden weergegeven door de volgende JSF-tags:


          <h:outputText value="#{form.saisie11}"/>
          ...
          <h:outputText value="#{form.saisie12}"/>
  • regels 22-23: als de eigenschap saisie11+saisie12=10 is aangevinkt, worden de twee velden errorSaisie11 en errorSaisie12 van het sjabloon leeggemaakt, zodat een eventuele eerdere foutmelding wordt gewist. Hierbij moet worden opgemerkt dat het sjabloon tussen de verzoeken door wordt bewaard in de sessie van de klant.

Hier volgt een voorbeeld van de uitvoering:

In de kolom [1] is te zien dat het model de verzonden waarden heeft ontvangen, wat aantoont dat alle validatie- en conversiebewerkingen tussen de verzonden waarden en het model zijn geslaagd. De gebeurtenishandler form.submit, die de klik op de knop [Valider] afhandelt, kon zo worden uitgevoerd. Deze gebeurtenisbeheerder heeft de berichten gegenereerd die worden weergegeven in [2] en [3]. We zien dat het model is bijgewerkt, terwijl het formulier is afgewezen en teruggestuurd naar de klant. Het zou wenselijk zijn dat het sjabloon in een dergelijk geval niet wordt bijgewerkt. Stel namelijk dat de gebruiker de bijwerking annuleert met de knop [Annuler] [4], dan is het niet mogelijk om terug te keren naar het oorspronkelijke sjabloon, tenzij dit is opgeslagen.

2.8.5.9. POST van een formulier zonder controle van de invoer

Laten we het bovenstaande formulier eens bekijken en aannemen dat de gebruiker, die zijn fouten niet begrijpt, het invullen van het formulier wil afbreken. Hij zal dan de knop [Annuler] gebruiken, die wordt gegenereerd door de volgende code JSF:


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

Op regel 4 luidt het bericht msg['cancel'] als volgt:


cancel=Annuler

De methode form.cancel die aan de knop [Annuler] is gekoppeld, wordt alleen uitgevoerd als het formulier geldig is. Dit hebben we al laten zien voor de methode form.submit die aan de knop [Valider] is gekoppeld. Als de gebruiker de invoer in het formulier wil annuleren, heeft het natuurlijk geen zin om de geldigheid van zijn invoer te controleren. Dit resultaat wordt bereikt met het attribuut immediate="true", dat JSF opdraagt de methode form.cancel uit te voeren zonder de validatie- en conversiefase te doorlopen. Laten we terugkeren naar de verwerkingscyclus van POST JSF:

De gebeurtenissen van de actiecomponenten <h:commandButton> en <h:commandLink> met het attribuut immediate="true" worden verwerkt in de fase [C], waarna de cyclus JSF direct doorgaat naar de fase [E] voor het weergeven van het antwoord.

De methode form.cancel is als volgt:


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

Als we de knop [Annuler] in het vorige formulier gebruiken, krijgen we de volgende pagina te zien:

  • krijgt men opnieuw het formulier te zien, omdat de gebeurtenisverwerker form.cancel de navigatiesleutel null retourneert. De pagina [index.xhtml] wordt dus teruggestuurd,
  • het sjabloon [Form.java] is gewijzigd door de methode form.cancel. Dit wordt weergegeven in de kolom [2], die dit sjabloon toont,
  • terwijl de kolom [3] de geboekte waarde voor de componenten weergeeft.

Laten we teruggaan naar de code JSF van de component saisie1 [4];


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

Regel 4: de waarde van de component saisie1 is gekoppeld aan het model form.saisie1. Dit heeft verschillende gevolgen:

  • bij een GET van [index.xhtml] zal de component saisie1 de waarde van het sjabloon form.saisie1 weergeven,
  • bij een POST van [index.xhtml], wordt de ingevulde waarde voor de component saisie1 alleen toegewezen aan het sjabloon form.saisie1 als alle validaties en conversies van het formulier succesvol zijn verlopen. Ongeacht of het model al dan niet is bijgewerkt door de verzonden waarden: als het formulier na afloop van POST wordt teruggestuurd, geven de componenten de verzonden waarde weer en niet de waarde van het model waaraan ze zijn gekoppeld. Dit is te zien op de bovenstaande schermafbeelding, waar de kolommen [2] en [3] niet dezelfde waarden hebben.

2.9. Voorbeeld mv-jsf2-07: gebeurtenissen in verband met de statuswijziging van componenten JSF

2.9.1. De applicatie

De applicatie toont een voorbeeld van POST dat is gerealiseerd zonder gebruik te maken van een knop of een link. Het formulier ziet er als volgt uit:

De inhoud van de lijst combo2 [2] is gekoppeld aan het geselecteerde element in combo1 [1]. Wanneer de selectie in [1] wordt gewijzigd, wordt een POST van het formulier uitgevoerd, waarbij de inhoud van combo2 wordt aangepast aan het geselecteerde element in [1], waarna het formulier wordt teruggestuurd. Tijdens dit POST-proces vindt er geen validatie plaats.

2.9.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

Er is één formulier [index.xhtml] met het bijbehorende sjabloon [Form.java].

2.9.3. De applicatieomgeving

Het berichtenbestand [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

Het 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. Het formulier [index.xhtml]

Het formulier [index.xhtml] ziet er als volgt uit:


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
  <h:head>
    <title>JSF</title>
    <h:outputStylesheet library="css" name="styles.css"/>
    ...
  </h:head>
  <h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
    <h2><h:outputText value="#{msg['app.titre2']}"/></h2>
    <h:form id="formulaire">
      <h:messages globalOnly="true"/>
      <h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
        <!-- headers -->
        <h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
        <h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
        <h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
        <!-- regel 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}"/>
        <!-- regel 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}"/>
        <!-- regel 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>
      <!-- bedieningsknoppen -->
      <h:panelGrid columns="2" border="0">
        <h:commandButton value="#{msg['submit']}"/>
        ...
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

De nieuwigheid zit in de code van de lijst combo1, regels 24-26. Er verschijnen nieuwe attributen:

  • onchange: attribuut HTML – definieert een functie of JavaScript-code die moet worden uitgevoerd wanneer het geselecteerde element in combo1 verandert. Hier verzendt de JavaScript-code submit() het formulier naar de server,
  • valueChangeListener: attribuut JSF – geeft de naam aan van de methode die aan de serverzijde moet worden uitgevoerd wanneer het in combo1 geselecteerde element verandert. In totaal worden er twee methoden uitgevoerd: één aan de clientzijde en één aan de serverzijde,
  • immediate=true: attribuut JSF - bepaalt het moment waarop de server-side gebeurtenishandler moet worden uitgevoerd: nadat het formulier is opgebouwd zoals de gebruiker het heeft ingevuld, maar vóór de validatiecontroles van de invoer. Het is hier de bedoeling om de lijst combo2 te vullen op basis van het geselecteerde element in de lijst combo1, zelfs als er elders in het formulier onjuiste invoer kan staan. Hier volgt een voorbeeld:
  • in [1], een eerste invoer,
  • in [2] wordt het geselecteerde element uit combo1 gewijzigd van A naar B.

Het verkregen resultaat is als volgt:

De bewerking POST heeft plaatsgevonden. De inhoud van combo2 en [2] is aangepast aan hetgeselecteerde element in combo1 [1], hoewel de invoer in [3] onjuist was. Het is het attribuut immediate=true dat ervoor heeft gezorgd dat de methode form.combo1ChangeListener vóór de geldigheidscontroles werd uitgevoerd. Zonder dit attribuut zou deze methode niet zijn uitgevoerd, omdat de verwerkingscyclus bij de validiteitscontroles zou zijn gestopt vanwege de fout in [3].

De bij het formulier behorende meldingen in [messages.properties] zijn als volgt:


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

De levensduur van [Form.java] is ingesteld op request:


package forms;

...

@ManagedBean
@RequestScoped
public class Form {

Op regel 6 wordt het bereik van de bean ingesteld op **request**.

2.9.5. Het sjabloon [Form.java]

Het model [Form.java] is als volgt:


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() {
  }
  
// formuliervelden
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // werkvelden
  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 initialiseren
    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 initialiseren op basis van combo1
    SelectItem[] combo2Items=new SelectItem[5];
    for(int i=1;i<=combo2Items.length;i++){
      combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
    }
    return combo2Items;
  }
  
  // listeners
  public void combo1ChangeListener(ValueChangeEvent event){
    // opvolging
    logger.info("combo1ChangeListener");
    // we halen de verzonden waarde van combo1 op
    combo1Label=(String)event.getNewValue();
    // we sturen het antwoord terug omdat we de validaties willen omzeilen
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // vervolg
    logger.info("raz");
    // formulier leegmaken
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
  
// getters - setters
  ...
}

Laten we het formulier [index.xhtml] koppelen aan het bijbehorende sjabloon [Form.java]:

De lijst combo1 wordt gegenereerd door de volgende code JSF:


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

De lijst haalt haar elementen op via de methode getCombo1Items van haar sjabloon (regel 2). Deze methode is gedefinieerd in de regels 28-35 van de Java-code. Ze genereert een lijst met drie elementen {"A", "B", "C"}.

De lijst combo2 wordt gegenereerd door de volgende code JSF:


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

De lijst haalt haar elementen op via de methode getCombo2Items van haar model (regel 2). Deze methode is gedefinieerd in de regels 37-44 van de Java-code. Ze genereert een lijst met vijf elementen {"X1", "X2", "X3", "X4", "X5"}, waarbij X het element combo1Label uit regel 16 is. Bij het voor het eerst genereren van het formulier bevat de lijst combo2 dus de elementen {"A1","A2","A3", "A4", "A5"}.

Wanneer de gebruiker het geselecteerde element in de lijst combo1 wijzigt,

  • wordt de gebeurtenis onchange="submit();" door de clientbrowser verwerkt. Het formulier wordt vervolgens naar de server verzonden,
  • aan de serverzijde zal JSF detecteren dat de waarde van de component combo1 is gewijzigd. De methode combo1ChangeListener in de regels 47-54 wordt uitgevoerd. Een methode van het type ValueChangeListener ontvangt als parameter een object van het type javax.faces.event.ValueChangeEvent. Met dit object kunnen de oude en de nieuwe waarde van de component die van waarde is veranderd, worden opgehaald met de volgende methoden:

Image

Hier is de component de lijst combo1 van het type UISelectOne. De waarde ervan is van het type String.

  • regel 51 van het Java-sjabloon: de nieuwe waarde van combo1 wordt opgeslagen in combo1Label, dat wordt gebruikt om de elementen van de lijst combo2 te genereren,
  • regel 53: het antwoord wordt teruggestuurd. Hierbij moet worden opgemerkt dat de handler combo1ChangeListener wordt uitgevoerd met het attribuut immediate="true". Hij wordt dus uitgevoerd na de fase waarin de componentboom van de pagina is bijgewerkt met de verzonden waarden en vóór het validatieproces van de verzonden waarden. We willen dit validatieproces echter vermijden, omdat de lijst combo2 moet worden bijgewerkt, zelfs als er elders in het formulier nog foutieve invoer staat. We vragen daarom dat het antwoord onmiddellijk wordt verzonden, zonder de fase van validatie van de invoer te doorlopen.
  • Het formulier wordt teruggestuurd zoals het is ingevuld. De elementen van de lijsten combo1 en combo2 zijn echter geen ingevoerde waarden. Ze worden opnieuw gegenereerd door de methoden getCombo1Items en getCombo2Items aan te roepen. Deze laatste methode gebruikt dan de nieuwe waarde van combo1Label die is vastgesteld door combo1ChangeListener, en de elementen van de lijst combo2 zullen veranderen.

2.9.6. De knop [Raz]

Met de knop [Raz] willen we het formulier terugzetten naar de oorspronkelijke staat, zoals hieronder weergegeven:

In [1] staat het formulier vóór de knop POST van de knop [Raz], en in [2] staat het resultaat van de knop POST.

Hoewel dit functioneel eenvoudig is, blijkt de afhandeling van dit gebruiksscenario vrij complex te zijn. Er kunnen verschillende oplossingen worden geprobeerd, met name die welke is gebruikt voor de knop [Annuler] uit het vorige voorbeeld:


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

waarbij de methode form.raz als volgt is:


  public String raz(){
    // formulier leegmaken
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
}

Het resultaat dat wordt verkregen met de knop [Raz] in het vorige voorbeeld is dan als volgt:

De kolom [1] laat zien dat de methode form.raz is uitgevoerd. De kolom [1] blijft echter de geplaatste waarden weergeven:

  • voor combo1 was de geboekte waarde „B“. Dit element is dus geselecteerd in de lijst,
  • voor combo2 was de geboekte waarde "B5". Als gevolg van de uitvoering van form.raz zijn de elementen {"B1", ..., "B5"} van combo2 gewijzigd in {"A1", ..., "A5"}. Het element "B5" bestaat niet meer en kan daarom niet worden geselecteerd. In dat geval wordt het eerste element van de lijst weergegeven;
  • voor saisie1 was de verzonden waarde 10.

Dit is de normale werking met het attribuut immediate=&quot;true&quot;. Om een ander resultaat te krijgen, moet je de waarden die je in het nieuwe formulier wilt zien, zelf verzenden, zelfs als de gebruiker andere waarden heeft ingevoerd. Dit doe je met een beetje JavaScript-code aan de clientzijde. Het formulier ziet er dan als volgt uit:


<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}"/>
  • regel 10: het attribuut onclick='raz()' geeft aan dat de JavaScript-functie raz moet worden uitgevoerd wanneer de gebruiker op de knop [Raz] klikt,
  • regel 3: we wijzen de waarde "A" toe aan het element HTML met de naam 'formulier:combo1'. De verschillende elementen van regel 3 zijn als volgt:
    • document: de pagina die door de browser wordt weergegeven,
    • document.forms: alle formulieren in het document,
    • document.forms['formulaire']: het formulier met het attribuut name="formulaire",
    • documents.forms['formulaire'].elements: alle elementen van het formulier met het attribuut name="formulaire",
    • document.forms['formulaire'].elements['formulaire:combo1']: formulierelement met het attribuut name="formulaire:combo1"
    • document.forms['formulaire'].elements['formulaire:combo1'].value: waarde die zal worden verzonden door het formulierelement met het attribuut name="formulaire:combo1".

Om de attributen name van de verschillende elementen van de door de browser weergegeven pagina te achterhalen, kunt u de broncode raadplegen (hieronder met IE7):

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

Nu dit is uitgelegd, is het duidelijk dat in de JavaScript-code van de functie raz:

  • regel 3 ervoor zorgt dat de waarde die voor de component combo1 wordt verzonden de tekenreeks A is,
  • regel 4 ervoor zorgt dat de waarde die naar de component combo2 wordt verzonden de tekenreeks A1 is,
  • regel 5 zorgt ervoor dat de waarde die voor de component saisie1 wordt verzonden, de tekenreeks 0 is.

Zodra dit is gebeurd, wordt de POST van het formulier, gekoppeld aan elke knop van het type <h:commandButton> (regel 10), geactiveerd. De methode form.raz wordt uitgevoerd en het formulier wordt teruggestuurd zoals het is verzonden. Het resultaat is dan als volgt:

Achter dit resultaat gaat veel schuil. De waarden „A”, „A1” en „0” van de componenten combo1, combo2 en saisie1 worden naar de server verzonden. Stel dat de vorige waarde van combo1 „B“ was. Dan is er een waardeverandering van de component combo1 en zou de methode form.combo1ChangeListener ook moeten worden uitgevoerd. Er zijn twee gebeurtenishandlers met het kenmerk immediate="true". Worden ze allebei uitgevoerd? Zo ja, in welke volgorde? Slechts één? Zo ja, welke?

Om hier meer over te weten te komen, maken we logbestanden aan in de applicatie:


package forms;

import java.util.logging.Logger;
...
public class Form {
  
...  
// formuliervelden
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // werkvelden
  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){
    // vervolging
    logger.info("combo1ChangeListener");
    // we halen de verzonden waarde van combo1 op
    combo1Label=(String)event.getNewValue();
    // we sturen het antwoord terug omdat we de validaties willen omzeilen
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // vervolg
    logger.info("raz");
    // formulier leegmaken
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
...
}
  • regel 16: er wordt een loggenerator aangemaakt. Met de parameter van getLogger kunnen we de herkomst van de logs onderscheiden. Hier heet de loggenerator forms.Form,
  • regel 21: we loggen het bezoek aan de methode combo1ChangeListener,
  • regel 30: er wordt gelogd dat de methode raz wordt aangeroepen.

Welke logberichten worden gegenereerd door de knop [Raz] of door de wijziging van de waarde van combo1? Laten we verschillende gevallen bekijken:

  • we gebruiken de knop [Raz] terwijl het geselecteerde element in combo1 "A" is. "A" is dus de laatste waarde van de component combo1. We hebben gezien dat de knop [Raz] een JavaScript-functie uitvoerde die de waarde "A" voor de component combo1 verstuurde. De waarde van de component combo1 verandert dus niet. Uit de logbestanden blijkt dan dat alleen de methode form.raz wordt uitgevoerd:
  
  • de knop [Raz] wordt gebruikt, terwijl het geselecteerde element in combo1 niet „A” is. De component combo1 verandert dus van waarde: de laatste waarde was niet "A" en de knop [Raz] zal de waarde "A" naar deze component sturen. Uit de logbestanden blijkt dan dat er twee methoden worden uitgevoerd. In deze volgorde: combo1ChangeListener, raz:
  
  • We wijzigen de waarde van combo1 zonder de knop [Raz] te gebruiken. Uit de logbestanden blijkt dat alleen de methode combo1ChangeListener wordt uitgevoerd:
  

2.10. Voorbeeld mv-jsf2-08: de tag <h:dataTable>

2.10.1. De applicatie

De applicatie toont een lijst met personen, met de mogelijkheid om deze te verwijderen:

  • in [1], een lijst met personen,
  • in [2], de links waarmee ze kunnen worden verwijderd.

2.10.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

Er is één formulier, [index.xhtml], met het bijbehorende sjabloon [Form.java].

2.10.3. De applicatieomgeving

Het configuratiebestand [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>

Het berichtenbestand [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

Het 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. Het formulier [index.xhtml] en het bijbehorende sjabloon [Form.java]

Ter herinnering: de weergave die bij de pagina [index.xhtml] hoort:

  

Het formulier [index.xhtml] is als volgt:


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

Op regel 14 gebruikt de tag <h:dataTable> het veld #{form.personnes} als gegevensbron. Dit ziet er als volgt uit:

private List<Personne> personen;

De klasse [Personne] is als volgt:


package forms;

public class Personne {
  // gegevens
  private int id;
  private String nom;
  private String prénom;
  
  // fabrikanten
  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);
  }
  
  // getters en setters
...
}

Laten we teruggaan naar de inhoud van de tag <h:dataTable>:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
  • het attribuut var="personne" bepaalt de naam van de variabele die de huidige persoon binnen de tag <h:datatable> vertegenwoordigt,
  • het attribuut headerClass="headers" bepaalt de stijl van de kolomkoppen van de tabel,
  • het attribuut columnClasses="...." bepaalt de stijl van elke kolom in de tabel.

Laten we eens kijken naar een van de kolommen van de tabel en zien hoe deze is opgebouwd:

  

De code XHTML van de kolom Id is als volgt:


<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>
  • regels 3-7: de kolom 'id' van de tabel,
  • regels 8-13: de kolom 'naam' van de tabel,
  • regels 14-19: de kolom 'voornaam' van de tabel.

Laten we nu eens kijken naar de kolom met links [Retirer]:

Deze kolom wordt gegenereerd door de volgende code:


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

De link [Retirer] wordt gegenereerd door de regels 4-6. Wanneer op de link wordt geklikt, wordt de methode [Form].retirerPersonne uitgevoerd. Het is tijd om de klasse [Form.java] te bekijken:


package forms;

import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean
@SessionScoped
public class Form {

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

  // constructor
  public Form() {
    // initialisatie van de lijst met personen
    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() {
    // de geselecteerde persoon opzoeken
    int i = 0;
    for (Personne personne : personnes) {
      // huidige persoon = geselecteerde persoon?
      if (personne.getId() == personneId) {
        // de huidige persoon uit de lijst verwijderen
        personnes.remove(i);
        // klaar
        break;
      } else {
        // volgende persoon
        i++;
      }
    }
    // testen op dezelfde pagina
    return null;
  }
  
  // getters en setters
...
}
  • regels 18-24: de constructor initialiseert de lijst met personen uit regel 14,
  • regel 10: omdat deze lijst gedurende de verzoeken moet blijven bestaan, is het bereik van de bean de sessie.

Wanneer de methode [retirerPersonne] op regel 26 wordt uitgevoerd, is het veld op regel 15 geïnitialiseerd met de id van de persoon waarvan de link [Retirer] is aangeklikt:


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

Met de tag <f:setPropertyActionListener> kan informatie naar het model worden overgedragen. Hier wordt de waarde van het attribuut **value gekopieerd naar het veld van het model dat wordt aangeduid door het attribuut **target. Zo wordt de id van de huidige persoon – degene die uit de lijst met personen moet worden verwijderd – via de getter van dit veld gekopieerd naar het veld [Form].personneId. Dit gebeurt vóór de uitvoering van de methode waarnaar wordt verwezen door het attribuut action in regel 1.

Regels 26-43: de methode [supprimerPersonne] verwijdert de persoon waarvan de id gelijk is aan personneId.

2.11. Voorbeeld mv-jsf2-09: lay-out van een JSF-toepassing

2.11.1. De applicatie

De applicatie laat zien hoe je een JSF-applicatie met twee weergaven opmaakt:

De applicatie heeft twee weergaven:

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

Je kunt tussen de twee pagina's navigeren. Wat we hier willen laten zien, is dat pagina 1 en 2 dezelfde opmaak hebben, zoals te zien is op de bovenstaande schermafbeeldingen.

2.11.2. Het NetBeans-project

Het NetBeans-project van de applicatie is als volgt:

De applicatie bevat alleen pagina's met de naam XHTML. Er is geen bijbehorend Java-sjabloon.

2.11.3. De pagina [layout.xhtml]

De pagina [layout.xhtml] bepaalt de opmaak van de pagina's van de applicatie:


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

Op regel 7 verschijnt een nieuwe naamruimte: ui. Deze naamruimte bevat de tags waarmee de pagina's van een applicatie kunnen worden opgemaakt. De tags uit deze naamruimte worden gebruikt op de regels 17, 22, 25 en 32.

De pagina [layout.xhtml] geeft informatie weer in een tabel HTML (regel 14). Deze pagina kan met een browser worden opgevraagd:

  • in [1], de opgevraagde URL.

Het veld [2] is gegenereerd door de volgende code XHTML:


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

Met de tag <ui:include> op regel 6 kan een externe code XHTML in de pagina worden opgenomen. Het bestand [entete.xhtml] is als volgt:


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

De volledige code van de regels 3-8 wordt ingevoegd in [layout.xhtml]. Zo worden de tags <html> en <body> ingevoegd in een tag <td>. Dit veroorzaakt geen fouten. De pagina’s die via <ui:include> worden ingevoegd, zijn dus volledige XHTML-pagina’s. Visueel gezien heeft alleen regel 6 effect. De tags <html> en <body> zijn aanwezig om syntactische redenen.

Het gebied [3] is gegenereerd door de volgende code XHTML:


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

De tag <ui:include> op regel 5 neemt het volgende bestand [menu.xhtml] op:


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

Het veld [4] is gegenereerd door de volgende code XHTML:


<h:form id="formulaire">
      <table style="width: 400px">
...
        <tr bgcolor="#ffcc66">
          <td colspan="2">
            <ui:include src="basdepage.xhtml"/>
          </td>
        </tr>         
      </table>
    </h:form>

De tag <ui:include> op regel 6 neemt het volgende bestand [basdepage.xhtml] op:


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

Het veld [5] is gegenereerd door de volgende code XHTML:


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

De tag <ui:insert> op regel 5 definieert een zone met de naam ‘inhoud’. Dit is een zone die variabele inhoud kan bevatten. We zullen zien hoe dat werkt. Toen we de pagina [layout.xhtml] opvroegen, was er geen inhoud gedefinieerd voor het gebied met de naam ‘inhoud’. In dat geval wordt de inhoud van de tag <ui:insert> in de regels 4-6 gebruikt. Regel 5 wordt dus weergegeven.

2.11.4. De pagina [page1.xhtml]

De pagina [layout.xhtml] is niet bedoeld om te worden weergegeven. Deze dient als sjabloon voor de pagina’s [page1.xhtml] en [page2.xhtml]. We spreken hier van paginasjablonen. De pagina [page1.xhtml] ziet er als volgt uit:


<?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 regel 6 wordt de naamruimte ui gebruikt,
  • in regel 7 wordt aangegeven dat de pagina is gekoppeld aan de sjabloon [layout.xhtml] met behulp van een <ui:composition>-tag,
  • regel 8: door deze koppeling wordt elke <ui:define>-tag gekoppeld aan een <ui:insert>-tag van de gebruikte sjabloon, in dit geval [layout.xhtml]. De koppeling vindt plaats via het name-attribuut van beide tags. Deze moeten identiek zijn.

De weergegeven pagina is [layout.xhtml], waarbij de inhoud van elke <ui:insert>-tag wordt vervangen door de inhoud van de <ui:define>-tag van de opgevraagde pagina. Hier verloopt alles alsof de weergegeven pagina de volgende is:


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

De regels 25-26 van [page1.xhtml] zijn ingevoegd in plaats van de tag <ui:insert> van [layout.xml].

De pagina [page2.xhtml] is vergelijkbaar met [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. Conclusion

De zojuist uitgevoerde analyse van JSF 2 is verre van volledig. Maar ze volstaat om de volgende voorbeelden te begrijpen. Voor meer informatie kunt u [ref2] raadplegen.

2.13. Testen met Eclipse

Laten we eens bekijken hoe u Maven-projecten kunt testen met de SpringSource Tool Suite:

  • in [1] importeren we een Maven-project [2] dat we selecteren met de knop [3]. We nemen hier het Maven-project [mv-jsf2-09] voor Eclipse
  • in [4] is het geïmporteerde project correct herkend als een Maven-project [5],
  • in [6], het geïmporteerde project in de projectverkenner,
  • in [7], wordt het uitgevoerd op een Tomcat-server [8] [9],
  • in [10] is Tomcat 7 gestart,
  • in [11] wordt de startpagina van het project [mv-jsf2-09] [11] weergegeven in een interne browser van Eclipse.