Skip to content

2. Java Server Faces

Przedstawiamy teraz framework Java Server Faces. Będziemy korzystać z wersji 2, jednak przykłady dotyczą głównie funkcji wersji 1. Z wersji 2 omówimy jedynie te funkcje, które są niezbędne dla przykładowej aplikacji, która pojawi się w dalszej części.

2.1. Rola JSF w aplikacji internetowej

Na początek spróbujmy umiejscowić JSF w procesie tworzenia aplikacji internetowej. Najczęściej aplikacja ta będzie oparta na architekturze wielowarstwowej, takiej jak poniższa:

  • warstwa [web] jest warstwą mającą kontakt z użytkownikiem aplikacji internetowej. Użytkownik ten wchodzi w interakcję z aplikacją internetową za pośrednictwem stron internetowych wyświetlanych w przeglądarce. To właśnie w tej warstwie znajduje się JSF i wyłącznie w tej warstwie,
  • warstwa [métier] realizuje reguły biznesowe aplikacji, takie jak obliczanie wynagrodzenia lub faktury. Warstwa ta wykorzystuje dane pochodzące od użytkownika za pośrednictwem warstwy [web] oraz z systemu zarządzania danymi (SGBD) za pośrednictwem warstwy [DAO],
  • warstwa [DAO] (Data Access Objects), warstwa [jpa] (Java Persistence API) oraz sterownik JDBC zarządzają dostępem do danych z systemu SGBD. Warstwa [jpa] pełni funkcję mapera obiektowo-relacyjnego (Object Relational Mapper). Stanowi ona pomost między obiektami obsługiwanymi przez warstwę [DAO] a wierszami i kolumnami danych w relacyjnej bazie danych,
  • integrację warstw można zrealizować za pomocą kontenera Spring lub EJB3 (Enterprise Java Bean).

Przykłady podane poniżej w celu zilustrowania działania JSF będą wykorzystywać tylko jedną warstwę, mianowicie warstwę [web]:

Po opanowaniu podstaw JSF będziemy tworzyć wielowarstwowe aplikacje Java EE.

2.2. Model rozwoju MVC oparty na JSF

JSF realizuje model architektury znany jako MVC (Model – Widok – Kontroler) w następujący sposób:

Architektura ta realizuje wzorzec projektowy MVC (Model, Widok, Kontroler). Obsługa żądania klienta przebiega zgodnie z następującymi czterema etapami:

  1. żądanie – przeglądarka klienta wysyła żądanie do kontrolera [Faces Servlet]. Kontroler ten obsługuje wszystkie żądania klientów. Stanowi on punkt wejścia do aplikacji. Jest to element „C” w nazwie MVC,
  2. przetwarzanie – kontroler C przetwarza to żądanie. W tym celu korzysta z pomocy menedżerów zdarzeń specyficznych dla napisanej aplikacji [2a]. Menedżerowie ci mogą potrzebować wsparcia ze strony warstwy biznesowej [2b]. Po przetworzeniu żądania klienta może ono wywołać różne reakcje. Klasycznym przykładem jest:
    • strona błędów, jeśli żądania nie udało się poprawnie przetworzyć;
    • w przeciwnym razie strona potwierdzenia,
  3. nawigacja – kontroler wybiera odpowiedź (= widok), która ma zostać wysłana do klienta. Wybór odpowiedzi do wysłania do klienta wymaga kilku kroków:
    • wybór Faceletu, który wygeneruje odpowiedź. Nazywa się to widokiem V, czyli V z MVC. Wybór ten zależy zazwyczaj od wyniku wykonania akcji żądanej przez użytkownika;
    • dostarczenie temu Faceletowi danych potrzebnych do wygenerowania tej odpowiedzi. W rzeczywistości zawiera ona najczęściej informacje obliczone przez kontroler. Informacje te tworzą tak zwany model M widoku, czyli M z MVC,

Etap 3 polega zatem na wyborze widoku V oraz stworzeniu modelu M niezbędnego do jego wyświetlenia.

  1. Odpowiedź – kontroler C żąda od wybranego Faceletu wyświetlenia strony. Facelet wykorzystuje model M przygotowany przez kontroler C do zainicjowania dynamicznych części odpowiedzi, którą ma wysłać do klienta. Dokładna forma tej odpowiedzi może być różna: może to być strumień HTML, PDF, plik Excel itp.

W projekcie JSF:

  • kontrolerem C jest serwlet [javax.faces.webapp.FacesServlet]. Znajduje się on w bibliotece [javaee.jar],
  • widoki V są realizowane przez strony wykorzystujące technologię Facelets,
  • modele M i menedżery zdarzeń są zaimplementowane przez klasy Java, często nazywane „backing beans” lub po prostu „beans”.

Teraz wyjaśnijmy związek między architekturą internetową MVC a architekturą warstwową. Są to dwie różne koncepcje, które czasami są mylone. Weźmy jako przykład jednowarstwową aplikację internetową JSF:

Jeśli zaimplementujemy warstwę [web] za pomocą JSF, otrzymamy wprawdzie architekturę internetową MVC, ale nie będzie to architektura wielowarstwowa. W tym przypadku warstwa [web] zajmie się wszystkim: prezentacją, logiką biznesową, dostępem do danych. W przypadku warstwy JSF zadanie to wykonają komponenty typu bean.

Rozważmy teraz wielowarstwową architekturę internetową:

Warstwa [web] może zostać zaimplementowana bez frameworku i bez stosowania się do modelu MVC. Mamy więc architekturę wielowarstwową, ale warstwa internetowa nie implementuje modelu MVC.

W MVC stwierdziliśmy, że model M odpowiada widokowi V, c.a.d, czyli zbiorowi danych wyświetlanych przez widok V. Często podaje się inną definicję modelu M z MVC:

Wielu autorów uważa, że to, co znajduje się po prawej stronie warstwy [web], tworzy model M z MVC. Aby uniknąć niejasności, będziemy mówić o:

  • o modelu domeny, gdy mamy na myśli wszystko, co znajduje się po prawej stronie warstwy [web],
  • o modelu widoku, gdy mamy na myśli dane wyświetlane przez widok V.

W dalszej części termin „model M” będzie odnosił się wyłącznie do modelu widoku V.

2.3. Przykład mv-jsf2-01: elementy projektu JSF

Pierwsze przykłady będą ograniczone wyłącznie do warstwy internetowej zaimplementowanej za pomocą JSF 2:

Po opanowaniu podstaw przeanalizujemy bardziej złożone przykłady z architekturami wielowarstwowymi.

2.3.1. Tworzenie projektu

Generujemy nasz pierwszy projekt JSF2 za pomocą NetBeans 7.

  
  • w [1], tworzymy nowy projekt,
  • w [2] wybieramy kategorię [Maven] i typ projektu [Web Application],
  • w [3], wyznaczyć folder nadrzędny dla folderu nowego projektu,
  • w [4] nadać nazwę projektowi,
  • w [5], wybrać serwer. W NetBeans 7 mamy do wyboru serwery Apache Tomcat i Glassfish. Różnica między nimi polega na tym, że Glassfish obsługuje EJB (Enterprise Java Bean), a Tomcat nie. W naszych przykładach JSF nie będziemy korzystać z EJB. W związku z tym możemy tutaj wybrać dowolny serwer,
  • w przypadku [6] wybieramy wersję Java EE 6 Web,
  • w [7] – wygenerowany projekt.

Przyjrzyjmy się elementom projektu i wyjaśnijmy rolę każdego z nich.

  • w pliku [1]: różne gałęzie projektu:
    • [Web Pages]: będzie zawierać strony internetowe (.xhtml, .jsp, .html), zasoby (obrazy, różne dokumenty), konfigurację warstwy internetowej oraz konfigurację frameworka JSF;
    • [Source packages]: klasy Java projektu;
    • [Dependencies]: archiwa .jar niezbędne dla projektu i zarządzane przez framework Maven;
    • [Java Dependencies]: archiwa .jar niezbędne dla projektu, które nie są zarządzane przez framework Maven;
    • [Project Files]: plik konfiguracyjny Maven i NetBeans,
  • w [2]: gałąź [Web Pages],

Zawiera ona następującą stronę [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>

Jest to strona internetowa wyświetlająca ciąg znaków „Hello World” dużymi literami.

Plik [META-INF/context.xml] ma następującą treść:


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

Wiersz 2 wskazuje, że kontekst aplikacji (lub jej nazwa) to /mv-jsf2-01. Oznacza to, że strony internetowe projektu będą wywoływane za pośrednictwem pliku URL o postaci http://machine:port/mv-jsf2-01/page. Domyślnym kontekstem jest nazwa projektu. Nie będziemy musieli modyfikować tego pliku.

  • na [3], gałąź [Source Packages],

Ta gałąź zawiera kod źródłowy klas Java projektu. Nie ma tu żadnych klas. NetBeans wygenerowało domyślny pakiet, który można usunąć: [4].

  • w [5], gałąź [Dependencies],

Ta gałąź wyświetla wszystkie biblioteki niezbędne dla projektu i zarządzane przez Maven. Wszystkie wymienione tutaj biblioteki zostaną automatycznie pobrane przez Maven. Dlatego projekt Maven wymaga dostępu do Internetu. Pobrane biblioteki zostaną zapisane lokalnie. Jeśli inny projekt potrzebuje biblioteki już obecnej lokalnie, nie zostanie ona ponownie pobrana. Zobaczymy, że ta lista bibliotek oraz repozytoria, w których można je znaleźć, są zdefiniowane w pliku konfiguracyjnym projektu Maven.

  • w [6] biblioteki niezbędne dla projektu, które nie są zarządzane przez Maven,
  • w [7] – pliki konfiguracyjne projektu Maven:
    • [nb-configuration.xml] to plik konfiguracyjny NetBeans. Nie będziemy się nim zajmować.
    • [pom.xml]: plik konfiguracyjny Mavena. POM oznacza Project Object Model. Czasami może zaistnieć potrzeba bezpośredniej edycji tego pliku.

Wygenerowany plik [pom.xml] wygląda następująco:


<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>
  • wiersze 5–8 definiują obiekt (artefakt) Java, który zostanie utworzony przez projekt Maven. Informacje te pochodzą z kreatora użytego podczas tworzenia projektu:

Obiekt Maven jest definiowany przez cztery właściwości:

  • [groupId]: informacja przypominająca nazwę pakietu. Tak więc biblioteki frameworka Spring mają groupId=org.springframework, a biblioteki frameworka JSF mają groupId=javax.faces,
  • [artifactId]: nazwa obiektu Maven. W grupie [org.springframework] znajdują się zatem następujące obiekty artifactId: spring-context, spring-core, spring-beans, ... W grupie [javax.faces] znajdują się elementy artifactId i jsf-api,
  • [version]: numer wersji artefaktu Maven. Zatem artefakt org.springframework.spring-core ma następujące wersje: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
  • [packaging]: format artefaktu, najczęściej war lub jar.

Nasz projekt Maven wygeneruje zatem plik [war] (wiersz 8) w grupie [istia.st] (wiersz 5), o nazwie [mv-jsf2-01] (wiersz 6) i wersji [1.0-SNAPSHOT] (wiersz 7). Te cztery informacje muszą jednoznacznie definiować artefakt Maven.

Wiersze 17–24 zawierają listę zależności projektu Maven, czyli listę bibliotek niezbędnych dla projektu. Każda biblioteka jest definiowana przez cztery informacje (groupId, artifactId, wersja, opakowanie). Gdy brakuje informacji packaging, jak w tym przypadku, wykorzystywany jest plik jar o nazwie packaging. Dodaje się do tego jeszcze jedną informację – scope, która określa, w jakich momentach cyklu życia projektu biblioteka jest potrzebna. Wartością domyślną jest „compile”, co oznacza, że biblioteka jest potrzebna zarówno podczas kompilacji, jak i podczas wykonywania. Wartość „provided” oznacza, że biblioteka jest potrzebna podczas kompilacji, ale nie podczas wykonywania. W tym przypadku podczas wykonywania zostanie ona dostarczona przez serwer Tomcat 7.

2.3.2. Uruchomienie projektu

Uruchamiamy projekt:

W pliku [1] uruchamiany jest projekt Maven. Serwer Tomcat zostaje wówczas uruchomiony, o ile nie był jeszcze uruchomiony. Uruchamiana jest również przeglądarka, a strona URL z kontekstu projektu jest wywoływana jako [2]. Ponieważ nie zażądano żadnego dokumentu, wykorzystywana jest strona index.html, index.jsp, index.xhtml, o ile istnieje. W tym przypadku będzie to strona [index.jsp].

2.3.3. System plików projektu Maven

  • [1]: system plików projektu znajduje się w zakładce [Files],
  • [2]: źródła Java znajdują się w folderze [src / main / java],
  • [3]: strony internetowe znajdują się w folderze [src / main / webapp],
  • [4]: folder [target] został utworzony podczas kompilacji projektu,
  • [5]: w tym przypadku kompilacja projektu utworzyła archiwum [mv-jsf2-01-1.0-SNAPSHOT.war]. To właśnie to archiwum zostało uruchomione przez serwer Tomcat.

2.3.4. Konfiguracja projektu dla JSF

Nasz obecny projekt nie jest projektem typu JSF. Brakuje mu bibliotek frameworka JSF. Aby przekształcić bieżący projekt w projekt typu JSF, należy postępować w następujący sposób:

  • na [1], otwieramy właściwości projektu,
  • w [2] wybieramy kategorię [Frameworks],
  • w [3] dodajemy framework,
  • W [4] wybieramy Java Server Faces,
  • w pliku [5] program NetBeans proponuje nam wersję 2.1 tego frameworka. Akceptujemy ją,
  • w pliku [6] projekt zostaje wzbogacony o nowe zależności.

Plik [pom.xml] został zmodyfikowany, aby odzwierciedlić tę nową konfigurację:


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

W wierszach 14–33 dodano nowe zależności. Maven pobiera je automatycznie. Szuka ich w tzw. repozytoriach. Domyślnie używane jest repozytorium centralne (Central Repository). Można dodać inne repozytoria za pomocą tagu <repository>. W tym przypadku dodano dwa repozytoria:

  • wiersze 46–51: repozytorium dla biblioteki JSF 2,
  • wiersze 52–57: repozytorium dla biblioteki JSTL 1.1.

Projekt został również wzbogacony o nową stronę internetową:

Strona [index.HTML] wygląda następująco:


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

Mamy tu plik XML (wiersz 1). Znajdują się w nim tagi z pliku HTML, ale w formacie XML. Nazywamy to XHTML. Technologia wykorzystywana do tworzenia stron internetowych za pomocą JSF 2 nazywa się Facelets. Dlatego też stronę XHTML nazywamy czasem stroną Facelet.

Wiersze 3–4 definiują tag <html> wraz z przestrzenią nazw XML (xmlns=XML Name Space).

  • W wierszu 3 zdefiniowano główną przestrzeń nazw http://www.w3.org/1999/xhtml,
  • wiersz 4 definiuje przestrzeń nazw http://java.sun.com/jsf/html dla tagów HTML. Tagi te będą miały prefiks h:, jak wskazuje xmlns:h. Tagi te znajdują się w wierszach 5, 7, 8 i 10.

Po napotkaniu deklaracji przestrzeni nazw serwer WWW przeszuka katalogi [META-INF] i Classpath aplikacji w poszukiwaniu plików z rozszerzeniem .tld (TagLib Definition). W tym przypadku znajdzie je w archiwum [jsf-impl.jar] i [1,2]:

Przyjrzyjmy się plikowi [3] oraz plikowi [HTML_basic.tld]:

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

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

<!-- ============== Elementy opisu biblioteki tagów ============= -->

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

<!-- ============== Walidator biblioteki tagów ============= -->
...
  • w wierszu 19 adres URI biblioteki znaczników,
  • w wierszu 16 – jej skrócona nazwa.

W tym pliku znajdują się definicje różnych znaczników <h:xx>. Znaczniki te są obsługiwane przez klasy Java, które również znajdują się w artefakcie [jsf-impl.jar].

Wróćmy do naszego projektu JSF. Został on wzbogacony o nową gałąź:

Gałąź [Other Sources] [1] zawiera pliki, które muszą znajdować się w ścieżce Classpath projektu i które nie są kodem Java. Dotyczy to plików komunikatów w JSF. Zauważyliśmy, że bez dodania frameworka JSF do projektu ta gałąź nie istnieje. Aby ją utworzyć, wystarczy utworzyć folder [src / main / resources] [3] w zakładce [Files] [2].

W rezultacie w gałęzi [Web Pages] pojawił się nowy folder:

Utworzono folder [WEB-INF], zawierający plik [web.xml] . Plik ten konfiguruje aplikację internetową:


<?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>
  • Wiersze 7–10 definiują serwlet c.a.d – klasę Java zdolną do przetwarzania żądań klientów. Aplikacja JSF działa w następujący sposób:

Architektura ta realizuje wzorzec projektowy MVC (Model, Widok, Kontroler). Przypomnijmy to, co zostało już napisane powyżej. Obsługa żądania klienta przebiega zgodnie z następującymi czterema etapami:

1 – żądanie – przeglądarka klienta wysyła żądanie do kontrolera [Faces Servlet]. Kontroler ten obsługuje wszystkie żądania klientów. Stanowi on bramę wejściową do aplikacji. Jest to „C” w nazwie MVC,

2 – przetwarzanie – kontroler C przetwarza to żądanie. W tym celu korzysta z pomocy menedżerów zdarzeń specyficznych dla napisanej aplikacji [2a]. Menedżerowie ci mogą potrzebować wsparcia ze strony warstwy biznesowej [2b]. Po przetworzeniu żądania klienta może ono wywołać różne odpowiedzi. Klasycznym przykładem jest:

  • strona błędów, jeśli żądania nie udało się poprawnie przetworzyć;
  • w przeciwnym razie strona potwierdzenia,

3 – nawigacja – kontroler wybiera odpowiedź (= widok), która ma zostać wysłana do klienta. Wybór odpowiedzi do wysłania do klienta wymaga kilku kroków:

  • wybór Faceletu, który wygeneruje odpowiedź. Nazywa się to widokiem V, czyli V z MVC. Wybór ten zależy zazwyczaj od wyniku wykonania akcji żądanej przez użytkownika;
  • dostarczenie temu Faceletowi danych potrzebnych do wygenerowania tej odpowiedzi. W rzeczywistości zawiera ona najczęściej informacje obliczone przez kontroler. Informacje te tworzą tak zwany model M widoku, czyli M z MVC,

Etap 3 polega zatem na wyborze widoku V oraz stworzeniu modelu M niezbędnego do jego wyświetlenia.

4 – odpowiedź – kontroler C żąda od wybranego Faceletu wyświetlenia strony. Facelet wykorzystuje model M przygotowany przez kontroler C do zainicjowania dynamicznych części odpowiedzi, którą musi wysłać do klienta. Dokładna forma tej odpowiedzi może być różna: może to być strumień HTML, PDF, plik Excel itp.

W projekcie JSF:

  • kontrolerem C jest serwlet [javax.faces.webapp.FacesServlet],
  • widoki V są realizowane przez strony wykorzystujące technologię Facelets,
  • modele M i menedżery zdarzeń są zaimplementowane przez klasy Java, często nazywane „backing beans” lub po prostu „Beans”.

Przyjrzyjmy się zawartości pliku [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>
  • wiersze 12–15: tag <servlet-mapping> służy do powiązania serwletu z plikiem URL żądanym przez przeglądarkę klienta. W tym przypadku wskazano, że pliki URL o formacie [/faces/*] mają być przetwarzane przez serwlet o nazwie [Faces Servlet]. Serwlet ten jest zdefiniowany w wierszach 7–10. Ponieważ w pliku nie ma żadnego innego elementu <servlet-mapping>, oznacza to, że serwlet o nazwie [Faces Servlet] będzie przetwarzał wyłącznie żądania URL o postaci [/faces/*]. Widzieliśmy, że kontekst aplikacji nosi nazwę [/mv-jsf2-01]. Pliki URL od klientów, przetwarzane przez serwlet [Faces Servlet], będą zatem miały postać [http://machine:port/mv-jsf2-01/faces/*]. Strony .html i .jsp będą domyślnie przetwarzane przez sam kontener serwletów, a nie przez konkretny serwlet. Kontener serwletów wie bowiem, jak je obsługiwać,
  • wiersze 7–10: definiują serwlet [Faces Servlet]. Ponieważ wszystkie przyjęte żądania typu URL są do niego kierowane, pełni on rolę kontrolera C w modelu MVC,
  • wiersz 10: wskazuje, że serwlet musi zostać załadowany do pamięci zaraz po uruchomieniu serwera WWW. Domyślnie serwlet jest ładowany dopiero po otrzymaniu pierwszego skierowanego do niego żądania,
  • wiersze 3–6: definiują parametr przeznaczony dla serwletu [Faces Servlet]. Parametr javax.faces.PROJECT_STAGE określa etap, na którym znajduje się uruchomiony projekt. Na etapie Development serwlet [Faces Servlet] wyświetla komunikaty o błędach przydatne podczas debugowania. Na etapie Production komunikaty te nie są już wyświetlane,
  • wiersze 17–19: czas trwania sesji w minutach. Klient komunikuje się z aplikacją poprzez serię cykli żądanie/odpowiedź. Każdy cykl wykorzystuje własne połączenie TCP-IP, które jest nawiązywane na nowo przy każdym kolejnym cyklu. Ponadto, jeśli klient C wysyła dwa żądania D1 i D2, serwer S nie ma możliwości ustalenia, że oba żądania należą do tego samego klienta C. Serwer S nie posiada pamięci klienta. Takie są zasady stosowanego protokołu HTTP (HyperText Transport Protocol): klient komunikuje się z serwerem poprzez serię cykli żądanie klienta / odpowiedź serwera, przy czym za każdym razem wykorzystywane jest nowe połączenie TCP-IP. Mówimy tu o protokole bezstanowym. W innych protokołach, takich jak na przykład FTP (File Transfer Protocol), klient C korzysta z tego samego połączenia przez cały czas trwania komunikacji z serwerem S. Połączenie jest zatem powiązane z konkretnym klientem. Serwer S zawsze wie, z kim ma do czynienia. Aby móc rozpoznać, że żądanie pochodzi od danego klienta, serwer WWW może wykorzystać technikę sesji:
    • przy pierwszym żądaniu od klienta serwer S wysyła mu oczekiwaną odpowiedź wraz z tokenem, czyli losowym ciągiem znaków, unikalnym dla tego klienta;
    • przy każdym kolejnym żądaniu klient C odsyła do serwera S otrzymany token, umożliwiając w ten sposób serwerowi S jego rozpoznanie.

Aplikacja ma teraz możliwość poproszenia serwera o zapamiętanie informacji związanych z danym klientem. Mówimy wtedy o sesji klienta. Wiersz 18 wskazuje, że czas trwania sesji wynosi 30 minut. Oznacza to, że jeśli klient C nie wyśle nowego żądania w ciągu 30 minut, jego sesja zostanie zamknięta, a zawarte w niej informacje utracone. Przy następnym żądaniu wszystko przebiegnie tak, jakby był on nowym klientem, i rozpocznie się nowa sesja,

  • wiersze 21–23: lista stron do wyświetlenia, gdy użytkownik żąda kontekstu bez podania konkretnej strony, na przykład tutaj [http://machine:port/mv-jsf2-01]. W takim przypadku serwer WWW (a nie serwlet) sprawdza, czy aplikacja zdefiniowała tag <welcome-file-list>. Jeśli tak, wyświetla pierwszą stronę znalezioną na liście. Jeśli ta nie istnieje, wyświetla drugą stronę i tak dalej, aż do znalezienia istniejącej strony. W tym przypadku, gdy klient żąda strony o adresie URL lub [http://machine:port/mv-jsf2-01], zostanie mu dostarczona strona o adresie URL lub [http://machine:port/mv-jsf2-01/index.xhtml].

2.3.5. Uruchomienie projektu

Po uruchomieniu nowego projektu w przeglądarce uzyskuje się następujący wynik:

  • w przypadku [1] zapytano o kontekst bez określenia dokumentu,
  • w przypadku [2], jak wyjaśniono, wyświetlana jest strona startowa (welcome-file) [index.xhtml].

Można z ciekawością przyjrzeć się otrzymanemu kodowi źródłowemu [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>

Otrzymaliśmy plik HTML. Wszystkie tagi <h:xx> z pliku index.xhtml zostały przetłumaczone na ich odpowiedniki w pliku HTML.

2.3.6. Lokalne repozytorium Maven

Wspomnieliśmy, że Maven pobiera zależności niezbędne dla projektu i przechowuje je lokalnie. Można przeglądać ten lokalny repozytorium:

  • w [1] wybieramy opcję [Window / Other / Maven Repository Browser],
  • w [2] otwiera się zakładka [Maven Repositories],
  • w [3] zawiera dwie gałęzie: jedną dla repozytorium lokalnego, drugą dla repozytorium centralnego. To ostatnie jest ogromne. Aby wyświetlić jego zawartość, należy zaktualizować jego indeks [4]. Aktualizacja ta trwa kilkadziesiąt minut.
  • w [5], biblioteki z lokalnego repozytorium,
  • w [6] znajduje się gałąź [istia.st], która odpowiada [groupId] naszego projektu,
  • W [7] można uzyskać dostęp do właściwości lokalnego repozytorium,
  • w [8] znajduje się ścieżka do lokalnego repozytorium. Warto ją znać, ponieważ czasami (rzadko) Maven nie korzysta już z najnowszej wersji projektu. Wprowadzamy zmiany i zauważamy, że nie są one uwzględniane. Można wtedy ręcznie usunąć gałąź z lokalnego repozytorium odpowiadającą naszemu [groupId]. To zmusza Maven do ponownego utworzenia gałęzi na podstawie najnowszej wersji projektu.

2.3.7. Wyszukiwanie artefaktu za pomocą Mavena

Nauczmy się teraz wyszukiwać artefakt za pomocą Mavena. Zacznijmy od listy aktualnych zależności pliku [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>

Wiersze 13–40 definiują zależności, a wiersze 45–58 repozytoria, w których można je znaleźć, oprócz repozytorium centralnego, które jest zawsze używane. Zmodyfikujemy zależności, aby korzystać z bibliotek w ich najnowszych wersjach.

Najpierw usuwamy obecne zależności [1]. Następnie modyfikujemy plik [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>
...
    <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>

W wierszach 5–12 usunięte zależności nie pojawiają się już w pliku [pom.xml]. Teraz poszukajmy ich w repozytoriach Maven.

  • w pliku [1] dodajemy zależność do projektu,
  • w [2] należy podać informacje o poszukiwanym artefakcie (groupId, artifactId, wersja, opakowanie (typ) i zakres). Zaczynamy od określenia [groupId] [3],
  • w polu [4] wpisujemy [espace], aby wyświetlić listę możliwych artefaktów. W tym przypadku są to [jsf-api] i [jsf-impl]. Wybieramy [jsf-api],
  • a w przypadku [5], postępując w ten sam sposób, wybieramy najnowszą wersję. Typ pakietu to jar.

Postępujemy w ten sposób dla wszystkich artefaktów:

W pliku [6] dodane zależności pojawiają się w projekcie. Plik [pom.xml] odzwierciedla te zmiany:


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

Załóżmy teraz, że nie znamy numeru [groupId] artefaktu, który nas interesuje. Na przykład chcemy użyć Hibernate jako ORM (mapera obiektowo-relacyjnego) i to wszystko, co wiemy. Możemy wówczas przejść na stronę [http://mvnrepository.com/]:

W polu [1] można wpisać słowa kluczowe. Wpiszmy hibernate i uruchommy wyszukiwanie.

  • w polu [2] wybierzmy [groupId], org.hibernate oraz [artifactId] i hibernate-core,
  • z [3] wybierzmy wersję 4.1.2-Final,
  • z [4] otrzymujemy kod Maven, który należy wkleić do pliku [pom.xml]. Robimy to.

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

Zapisujemy plik [pom.xml]. Maven rozpoczyna wówczas pobieranie nowych zależności. Projekt zmienia się w następujący sposób:

  • w [5], zależność [hibernate-core-4.1.2-Final]. W repozytorium, w którym został znaleziony, ten plik [artifactId] jest również opisany przez plik [pom.xml]. Plik ten został odczytany, a Maven wykrył, że plik [artifactId] posiada zależności. Pobiera je również. Czynność tę wykona dla każdego pobranego pliku [artifactId]. W rezultacie w pliku [6] znajdują się zależności, o które nie prosiliśmy bezpośrednio. Są one oznaczone ikoną inną niż ta w głównym pliku [artifactId].

W niniejszym dokumencie wykorzystujemy Maven głównie ze względu na tę cechę. Dzięki temu nie musimy znać wszystkich zależności biblioteki, z której chcemy skorzystać. Zarządzaniem nimi zajmuje się Maven. Ponadto, udostępniając plik [pom.xml] między programistami, mamy pewność, że każdy z nich korzysta z tych samych bibliotek.

W poniższych przykładach podamy jedynie nazwę używanego pliku [pom.xml]. Czytelnik będzie musiał jedynie z niego skorzystać, aby odtworzyć warunki przedstawione w niniejszym dokumencie. Ponadto projekty Maven są obsługiwane przez główne środowiska programistyczne Java (Eclipse, NetBeans, IntelliJ, JDeveloper). Dzięki temu czytelnik będzie mógł użyć swojego ulubionego środowiska do przetestowania przykładów.

2.4. Przykład mv-jsf2-02: menedżer zdarzeń – internacjonalizacja – nawigacja między stronami

2.4.1. Aplikacja

Aplikacja wygląda następująco:

  • w [1] – strona główna aplikacji,
  • w [2] znajdują się dwa linki do zmiany języka stron aplikacji,
  • w [3] – link do innej strony,
  • po kliknięciu [3] wyświetla się strona [4],
  • link [5] pozwala powrócić do strony głównej.
  • na stronie głównej [1] linki [2] umożliwiają zmianę języka,
  • na stronie [3] znajduje się strona główna w języku angielskim.

2.4.2. Projekt NetBeans

Wygenerujemy nowy projekt internetowy zgodnie z opisem w punkcie 2.3.1. Nazwiemy go mv-jsf2-02:

  • w [1], wygenerowany projekt,
  • w [2] usunięto pakiet [istia.st.mvjsf202] oraz plik [index.jsp],
  • W pliku [3] dodano zależności Maven za pomocą następującego pliku [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>

Dodane zależności dotyczą frameworka JSF. Wystarczy skopiować powyższe wiersze do pliku [pom.xml], zastępując nimi stare zależności.

  • w [4, 5]: tworzymy folder [src / main / resources] w zakładce [Files],
  • w [6], w zakładce [Projects], co spowodowało utworzenie gałęzi [Other Sources].

Mamy teraz projekt o nazwie JSF. Utworzymy w nim różne typy plików:

  • strony internetowe w formacie XHTML,
  • klasy Java,
  • pliki komunikatów,
  • plik konfiguracyjny projektu JSF.

Zobaczmy, jak utworzyć każdy z tych typów plików:

  • w pliku [1] tworzymy stronę JSF
  • w pliku [2] tworzymy stronę [index.xhtml] w formacie [Facelets] [3],
  • w [4] utworzono dwa pliki: [index.xhtml] i [WEB-INF / web.xml].

Plik [web.xml] konfiguruje aplikację JSF. Wygląda on następująco:


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

Omówiliśmy już ten plik w punkcie 2.3.4. Przypomnijmy jego główne właściwości:

  • wszystkie pliki typu URL, takie jak faces/*, są przetwarzane przez serwlet [javax.faces.webapp.FacesServlet],
  • strona [index.xhtml] jest stroną główną aplikacji.

Utworzony plik [index.xhtml] ma następującą treść:


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

Z tym plikiem mieliśmy już do czynienia w punkcie 2.3.4.

Teraz utwórzmy klasę Java:

  • w pliku [1] tworzymy klasę Java w gałęzi [Source Packages],
  • w [2] nadajemy jej nazwę i umieszczamy ją w pakiecie [3],
  • w gałęzi [4] utworzona klasa pojawia się w projekcie.

Kod utworzonej klasy stanowi szkielet klasy:


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

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

Na koniec utwórzmy plik komunikatów:

  • w [1], tworzymy plik [Properties],
  • w [2] podajemy nazwę pliku, a w [3] jego folder,
  • w [4] plik [messages.properties] został utworzony.

Czasami konieczne jest utworzenie pliku [WEB-INF/faces-config.xml] w celu skonfigurowania projektu JSF. Plik ten był obowiązkowy w przypadku pliku JSF 1. W przypadku pliku JSF 2 jest on opcjonalny. Jest on jednak niezbędny, jeśli strona JSF jest zinternacjonalizowana. Tak będzie w dalszej części. Pokażemy więc teraz, jak utworzyć ten plik konfiguracyjny.

  • w [1] tworzymy plik konfiguracyjny JSF,
  • w przypadku pliku [2] nadajemy mu nazwę, a w przypadku pliku [3] wskazujemy jego folder,
  • w pliku [4] – utworzony plik.

Utworzony plik [faces-config.xml] ma następującą treść:


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

Tagiem głównym jest <faces-config>. Treść tego tagu jest pusta. Będziemy musieli ją uzupełnić.

Mamy teraz wszystkie elementy potrzebne do utworzenia projektu JSF. W poniższych przykładach przedstawimy kompletny projekt JSF, a następnie omówimy jego elementy jeden po drugim. Teraz przedstawimy projekt, aby wyjaśnić następujące pojęcia:

  • zarządzania zdarzeniami w formularzu,
  • internacjonalizacji stron serwisu JSF,
  • nawigacji między stronami.

Projekt [mv-jsf2-02] wygląda następująco. Czytelnik może go znaleźć na stronie z przykładami (patrz punkt 1.2).

  • w [1], pliki konfiguracyjne projektu JSF,
  • w [2] – strony projektu JSF,
  • w [3] – jedyną klasę Java,
  • do [4] – pliki komunikatów.

2.4.3. Strona [index.xhtml]

Plik [index.xhtml] [1] wysyła stronę [2] do przeglądarki klienta:

Kod generujący tę stronę jest następujący:


<?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>
  • wiersze 7–9: przestrzenie nazw / biblioteki tagów wykorzystywane przez stronę. Tagi z prefiksem „h” to tagi HTML, natomiast tagi z prefiksem „f” to tagi specyficzne dla JSF,
  • wiersz 10: tag <f:view> służy do wyznaczenia obszaru kodu, który ma zostać przetworzony przez silnik JSF – czyli tego, w którym pojawiają się tagi <f:xx>. Atrybut „locale” pozwala określić język wyświetlania strony. W tym przypadku użyjemy dwóch języków: angielskiego i francuskiego. Wartość atrybutu `local` jest wyrażona w postaci wyrażenia EL (Expression Language) #{wyrażenie}. Forma wyrażenia może być różna. Najczęściej będziemy je zapisywać w postaci `bean['clé']` lub `bean.champ`. W naszych przykładach bean będzie albo klasą Java, albo plikiem komunikatów. W przypadku JSF 1 te bean musiały być zadeklarowane w pliku [faces-config.xml]. W przypadku JSF 2 nie jest to już obowiązkowe dla klas Java. Można teraz używać adnotacji, które sprawiają, że klasa Java staje się beanem rozpoznawanym przez JSF 2. Plik komunikatów musi być natomiast zadeklarowany w pliku konfiguracyjnym [faces-config.xml].

2.4.4. Bean [changeLocale]

W wyrażeniu EL #{changeLocale.locale}:

  • changeLocale to nazwa bean, w tym przypadku klasy Java ChangeLocale,
  • locale jest polem klasy ChangeLocale. Wyrażenie jest obliczane przez [ChangeLocale].getLocale(). Ogólnie rzecz biorąc, wyrażenie #{bean.champ} jest obliczane jako [Bean].getChamp(), gdzie [Bean] jest instancją klasy Java, której nadano nazwę bean oraz getChamp, getter powiązany z polem champ tego bean.

Klasa ChangeLocale ma następującą postać:


package utils;

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

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // ustawienia regionalne stron
  private String locale="fr";
  
  public ChangeLocale() {
  }
  
  ...
  public String getLocale() {
    return locale;
  }
  
}
  • wiersz 11: pole local,
  • wiersz 17: jego metoda pobierająca,
  • wiersz 7: adnotacja ManagedBean sprawia, że klasa Java ChangeLocale staje się beanem rozpoznawanym przez JSF. Bean jest identyfikowany za pomocą nazwy. Nazwę tę można określić za pomocą atrybutu name adnotacji: @ManagedBean(name= "xx "). W przypadku braku atrybutu name wykorzystywana jest nazwa klasy, przy czym jej pierwsza litera jest zamieniana na małą. Nazwa komponentu ChangeLocale to zatem changeLocale. Należy zwrócić uwagę na fakt, że adnotacja ManagedBean należy do pakietu javax.faces.bean.ManagedBean, a nie do pakietu javax.annotations.ManagedBean.
  • wiersz 8: adnotacja SessionScoped określa zakres działania bean. Istnieje kilka takich adnotacji. Najczęściej będziemy korzystać z trzech następujących:
    • RequestScoped: czas życia bean-a odpowiada cyklowi żądania przeglądarki / odpowiedzi serwera. Jeśli do przetworzenia nowego żądania z tej samej lub innej przeglądarki ten bean będzie ponownie potrzebny, zostanie on ponownie zainicjowany,
    • SessionScoped: czas życia bean odpowiada czasowi trwania sesji konkretnego klienta. Bean jest początkowo tworzony na potrzeby jednego z żądań tego klienta. Następnie pozostaje w pamięci w ramach sesji tego klienta. Taki bean zazwyczaj przechowuje dane specyficzne dla danego klienta. Zostanie zniszczony wraz z zakończeniem sesji klienta,
    • ApplicationScoped: czas życia bean odpowiada czasowi życia samej aplikacji. Bean o takim czasie życia jest najczęściej współdzielony przez wszystkich klientów aplikacji. Zazwyczaj jest on inicjowany na początku działania aplikacji.

Adnotacje te występują w dwóch pakietach: javax.enterprise.context.SessionScoped (JSF 2) oraz javax.faces.bean.SessionScoped (JSF 1). W tym przypadku korzystamy z pakietu JSF 2. Wymaga to od nas utworzenia pliku [WEB-INF / beans.xml]:

  

Plik ten jest generowany automatycznie przez NetBeans podczas importowania pakietu [javax.enterprise.context.SessionScoped]. Jego zawartość jest następująca:


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

Poza tagiem głównym <beans> plik jest pusty. To wystarczy. Wymagana jest jedynie jego obecność.

Na koniec warto zauważyć, że klasa [ChangeLocale] implementuje interfejs [Serializable]. Jest to wymagane w przypadku beanów o zasięgu Session, które serwer WWW może być zmuszony do serializacji do plików. W późniejszym terminie powrócimy do bean’a [ChangeLocale].

2.4.5. Plik komunikatów

Wróćmy do pliku [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>
  • wiersz 8: tag <h:outputText> wyświetla wartość wyrażenia EL #{msg['welcome.titre']} w postaci #{bean['champ']}. bean to nazwa klasy Java lub nazwa pliku komunikatów. W tym przypadku jest to nazwa pliku komunikatów. Plik ten musi być zadeklarowany w pliku konfiguracyjnym [faces-config.xml]. Bean msg jest zadeklarowany w następujący sposób:

<?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>
  • wiersze 11–18: tag <application> służy do konfiguracji aplikacji JSF,
  • wiersze 12–17: tag <resource-bundle> służy do definiowania zasobów dla aplikacji, w tym przypadku pliku komunikatów,
  • wiersze 13–15: tag <base-name> określa nazwę pliku komunikatów,
  • wiersz 14: plik będzie nosił nazwę messages[_CodeLangue][_CodePays].properties. Tag <base-name> określa jedynie pierwszą część nazwy. Reszta jest domyślna. Może istnieć kilka plików komunikatów, po jednym dla każdego języka:
  • w pliku [1] widoczne są cztery pliki komunikatów odpowiadające nazwie bazowej „messages” zdefiniowanej w pliku [faces-config.xml],
    • messages_fr.properties: zawiera komunikaty w języku francuskim (kod fr);
    • messages_en.properties: zawiera komunikaty w języku angielskim (kod en);
    • messages_es_ES.properties: zawiera komunikaty w języku hiszpańskim (kod es) z Hiszpanii (kod ES). Istnieją inne odmiany języka hiszpańskiego, na przykład z Boliwii (es_BO);
    • messages.properties: jest używany przez serwer, gdy język komputera, na którym działa, nie ma przypisanego żadnego pliku komunikatów. Zostałby użyty na przykład, gdyby aplikacja działała na komputerze w Niemczech, gdzie domyślnym językiem byłby niemiecki (de). Ponieważ nie istnieje plik [messages_de.properties], aplikacja używałaby pliku [messages.properties],
  • w przypadku pliku [2]: kody języków podlegają międzynarodowemu standardowi,
  • w pliku [3]: to samo dotyczy kodów krajów.

Nazwa pliku komunikatów jest zdefiniowana w wierszu 14. Będzie on wyszukiwany w pliku Classpath projektu. Jeśli znajduje się on w pakiecie, należy go zdefiniować w wierszu 14, na przykład ressources.messages, jeśli plik [messages.properties] znajduje się w folderze [ressources] projektu Classpath. Ponieważ nazwa w wierszu 14 nie zawiera nazwy pakietu, plik [messages.properties] należy umieścić w katalogu głównym folderu [src / main / resources]:

W pliku [1], w zakładce [Projects] projektu NetBeans, plik [messages.properties] jest przedstawiony jako lista różnych zdefiniowanych wersji komunikatów. Wersje są oznaczone ciągiem od jednego do trzech kodów [codeLangue_codePays_codeVariante]. W pliku [1] użyto wyłącznie kodu [codeLangue]: „en” dla języka angielskiego, „fr” dla języka francuskiego. Każda wersja stanowi osobny plik w systemie plików.

W naszym przykładzie plik komunikatów w języku francuskim [messages_fr.properties] będzie zawierał następujące elementy:


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

Z kolei plik [messages_en.properties] będzie wyglądał następująco:


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

Plik [messages.properties] jest identyczny z plikiem [messages_en.properties]. Ostatecznie przeglądarka klienta będzie miała do wyboru strony w języku francuskim i angielskim.

Wróćmy do pliku [faces-config.xml], który definiuje plik komunikatów:


...

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

Wiersz 8 wskazuje, że wiersz z pliku komunikatów będzie odwołany za pomocą identyfikatora msg na stronach JSF. Identyfikator ten jest używany w analizowanym pliku [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>

Tag <h:outputText> w wierszu 8 wyświetli wartość komunikatu (obecność identyfikatora msg) o kluczu welcome.titre. Komunikat ten jest wyszukiwany i znajdowany w pliku [messages.properties] dla aktualnie aktywnego języka. Na przykład dla języka francuskiego:


welcome.titre=Tutoriel JSF (JavaServer Faces)

Komunikat ma postać klucz=wartość. Wiersz 8 pliku [index.xhtml] po obliczeniu wyrażenia #{msg['welcome.titre']} przyjmuje następującą postać:


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

Ten mechanizm plików komunikatów pozwala w łatwy sposób zmienić język stron projektu JSF. Mówi się o internacjonalizacji projektu lub, częściej, o jej skrócie i18n, ponieważ słowo „internacjonalizacja” zaczyna się na literę „i”, a kończy na „n”, a między „i” a „n” znajduje się 18 liter.

2.4.6. Formularz

Kontynuujmy przeglądanie zawartości pliku [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>
  • wiersze 11–18: tag <h:form> wprowadza formularz. Formularz zazwyczaj składa się z:
    • tagów pól wprowadzania danych (tekst, przyciski opcji, pola wyboru, listy elementów itp.);
    • tagów walidacji formularza (przyciski, linki). To właśnie za pomocą przycisku lub linku użytkownik wysyła wprowadzone dane do serwera, który je przetworzy,

Każdy tag JSF może być identyfikowany za pomocą atrybutu id. Najczęściej można się bez niego obejść i tak właśnie zrobiono w przypadku większości tagów JSF użytych w tym przykładzie. Niemniej jednak atrybut ten jest przydatny w niektórych przypadkach. W wierszu 17 formularz jest identyfikowany za pomocą identyfikatora id formularza. W tym przykładzie identyfikator id formularza nie będzie używany i można go było pominąć.

  • wiersze 18–21: tag <h:panelGrid> definiuje tutaj tabelę HTML z dwiema kolumnami. Powoduje on utworzenie tagu <table>,
  • formularz zawiera trzy linki uruchamiające jego przetwarzanie, w wierszach 19, 20 i 23. Tag <h:commandLink> ma co najmniej dwa atrybuty:
    • value: tekst linku;
    • action: albo ciąg znaków C, albo odwołanie do metody, która po wykonaniu zwraca ciąg znaków C. Ten ciąg znaków C może być:
      • albo nazwą strony JSF w projekcie,
      • albo nazwą zdefiniowaną w regułach nawigacji pliku [faces-config.xml] i powiązaną ze stroną JSF projektu;

W obu przypadkach strona JSF zostanie wyświetlona po wykonaniu akcji zdefiniowanej przez atrybut action.

Przyjrzyjmy się mechanizmowi przetwarzania formularzy na przykładzie linku w wierszu 13:


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

Najpierw wykorzystywany jest plik komunikatów w celu zastąpienia wyrażenia #{msg['welcome.langue1']} jego wartością. Po obliczeniu znacznik przyjmuje postać:


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

Tłumaczenie HTML tego znacznika JSF będzie wyglądało następująco:

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

co da następujący efekt wizualny:

Warto zwrócić uwagę na atrybut onclick tagu HTML <a>. Gdy użytkownik kliknie link [Français], zostanie wykonany kod JavaScript. Kod ten jest osadzony na stronie otrzymanej przez przeglądarkę i to właśnie przeglądarka go wykonuje. Kod JavaScript jest szeroko stosowany w technologiach JSF i AJAX (Asynchronous JavaScript and XML). Jego głównym celem jest zazwyczaj poprawa ergonomii i responsywności aplikacji internetowych. Najczęściej jest on generowany automatycznie przez narzędzia programowe i w takim przypadku nie ma potrzeby jego rozumienia. Czasami jednak programista może być zmuszony do dodania kodu JavaScript do swoich stron JSF. Wówczas znajomość JavaScriptu jest niezbędna.

W tym przypadku nie ma potrzeby rozumienia kodu JavaScript wygenerowanego dla tagu JSF <h:commandLink>. Można jednak zwrócić uwagę na dwie kwestie:

  • kod JavaScript wykorzystuje identyfikator formularza, który nadaliśmy tagowi JSF <h:form>,
  • JSF generuje automatyczne identyfikatory dla wszystkich tagów, w których nie zdefiniowano atrybutu id. Przykład takiego tagu widzimy tutaj: j_idt8. Nadanie tagom jasnych identyfikatorów pozwala lepiej zrozumieć wygenerowany kod JavaScript, jeśli zajdzie taka potrzeba. Dzieje się tak zwłaszcza wtedy, gdy programista musi samodzielnie dodać kod JavaScript, który manipuluje komponentami strony. W takiej sytuacji musi znać identyfikatory id tych komponentów.

Co się stanie, gdy użytkownik kliknie link [Français] na powyższej stronie? Rozważmy architekturę aplikacji JSF:

Kontroler [Faces Servlet] otrzyma żądanie z przeglądarki klienta w następującej postaci: 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 
  • wiersze 1–2: przeglądarka żąda URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. Zawsze tak jest: dane wprowadzone w formularzu JSF, uzyskanym początkowo za pomocą URL i URLFormulaire, są wysyłane do tego samego URL. Przeglądarka ma dwa sposoby wysyłania wprowadzonych wartości: GET i POST. Przy użyciu metody GET wprowadzone wartości są wysyłane przez przeglądarkę w żądanym formularzu URL. W powyższym przykładzie przeglądarka mogłaby wysłać następujący pierwszy wiersz:

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

W przypadku zastosowanej tutaj metody POST przeglądarka wysyła do serwera wprowadzone wartości za pomocą wiersza 6.

  • wiersz 3: określa formę kodowania wartości formularza,
  • wiersz 4: określa rozmiar w bajtach wiersza 6,
  • wiersz 5: pusty wiersz, który oznacza koniec nagłówków HTTP i początek 126 bajtów wartości formularza,
  • wiersz 6: wartości formularza w postaci element1=wartość1&element2=wartość2& ..., w formacie kodowania określonym w wierszu 3. W tym formacie kodowania niektóre znaki są zastępowane ich wartościami szesnastkowymi. Tak jest w przypadku ostatniego elementu:

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

gdzie %3A reprezentuje znak :. Do serwera wysyłany jest zatem ciąg: formularz:j_idt8=formularz:j_idt8. Być może pamiętamy, że identyfikator j_idt8 pojawił się już wcześniej, gdy analizowaliśmy kod HTML wygenerowany dla tagu


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

Został on wygenerowany automatycznie przez JSF. Istotne jest tutaj to, że obecność tego identyfikatora w ciągu wartości wysyłanych przez przeglądarkę kliencką pozwala JSF stwierdzić, że kliknięto link [Français]. Następnie wykorzysta powyższy atrybut action, aby zdecydować, jak przetworzyć otrzymany ciąg znaków. Atrybut action="#{changeLocale.setFrenchLocale}" wskazuje JSF, że żądanie klienta powinno zostać przetworzone przez metodę [setFrenchLocale] obiektu o nazwie changeLocale. Przypomnijmy, że ten bean został zdefiniowany za pomocą adnotacji w klasie Java [ChangeLocale]:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

Nazwa bean jest definiowana przez atrybut name adnotacji @ManagedBean. W przypadku braku tego atrybutu jako nazwa bean wykorzystywana jest nazwa klasy, przy czym pierwsza litera jest zamieniana na małą.

Wróćmy do zapytania przeglądarki:

oraz do tagu <h:commandLink>, który wygenerował link [Français], w który kliknęliśmy:


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

Kontroler przekaże żądanie przeglądarki do obsługi zdarzeń zdefiniowanej przez atrybut action tagu <h:commandLink>. Obsługa zdarzeń M, do której odwołuje się atrybut action polecenia <h:commandLink>, musi mieć następującą sygnaturę:

public String M();
  • nie przyjmuje żadnych parametrów. Zobaczymy, że mimo to może mieć dostęp do żądania klienta,
  • musi zwrócić wynik C typu String. Ten ciąg znaków C może być:
    • albo nazwą strony JSF w projekcie;
    • albo nazwą zdefiniowaną w regułach nawigacji pliku [faces-config.xml] i powiązaną ze stroną JSF w projekcie;
    • albo wskaźnikiem null, jeśli przeglądarka klienta nie ma przechodzić na inną stronę,

W powyższej architekturze JSF kontroler [Faces Servlet] wykorzysta ciąg znaków C zwrócony przez menedżera zdarzeń oraz ewentualnie swój plik konfiguracyjny [faces-config.xml], aby określić, którą stronę JSF ma wysłać w odpowiedzi do klienta [4].

W tagu


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

menedżer zdarzenia „kliknięcie linku” [Français] to metoda [changeLocale.setFrenchLocale], gdzie changeLocale jest instancją klasy [utils.ChangeLocale] , którą już omówiliśmy:


package utils;

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

@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
  // ustawienia regionalne stron
  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;
  }
}

Metoda setFrenchLocale rzeczywiście ma sygnaturę typową dla procedur obsługi zdarzeń. Przypomnijmy, że procedura obsługi zdarzeń musi przetworzyć żądanie klienta. Skoro nie otrzymuje ona żadnych parametrów, w jaki sposób może uzyskać dostęp do tego żądania? Istnieje na to kilka sposobów:

  • Bean B, który zawiera moduł obsługi zdarzeń strony JSF P, często jest również tym, który zawiera model M tej strony. Oznacza to, że bean B zawiera pola, które zostaną zainicjowane wartościami wprowadzonymi na stronie P. Zostanie to wykonane przez kontroler [Faces Servlet] przed wywołaniem obsługi zdarzeń beana B. Obsługa ta będzie zatem miała dostęp, poprzez pola beana B, do którego należy, do wartości wprowadzonych przez klienta w formularzu i będzie mogła je przetwarzać.
  • Metoda statyczna [FacesContext.getCurrentInstance()] typu [FacesContext] zapewnia dostęp do kontekstu wykonania bieżącego żądania JSF, który jest obiektem typu [FacesContext]. Uzyskany w ten sposób kontekst wykonania zapytania umożliwia dostęp do parametrów przesłanych na serwer przez przeglądarkę klienta za pomocą następującej metody:
Map FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap()

Jeśli parametry przesłane (POST) przez przeglądarkę klienta są następujące:

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

metoda getRequestParameterMap() zwróci następujący słownik:

klucz
wartość
formularz
formularz
javax.faces.ViewState
...
formularz:j_id_id21
formularz:j_id_id21

W tagu


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

czego oczekuje się od menedżera zdarzeń locale.setFrenchLocale? Chcemy, aby ustawił język używany przez aplikację. W żargonie Java nazywa się to „lokalizacją” aplikacji. Ta lokalizacja jest wykorzystywana przez tag <f:view> na stronie JSF [index.xhtml]:


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

Aby przełączyć stronę na język francuski, wystarczy, aby atrybut „local” miał wartość „fr”. Aby przełączyć ją na język angielski, należy nadać mu wartość „en”. Wartość atrybutu „local” uzyskuje się za pomocą wyrażenia [ChangeLocale].getLocale(). Wyrażenie to zwraca wartość pola locale klasy [ChangeLocale]. Na tej podstawie można wywnioskować kod metody [ChangeLocale].setFrenchLocale(), która powinna przełączać strony na język francuski:


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

Wyjaśniliśmy, że menedżer zdarzeń musi zwrócić ciąg znaków typu C, który zostanie wykorzystany przez metodę [Faces Servlet] w celu znalezienia strony JSF, która ma zostać wysłana w odpowiedzi do przeglądarki klienta. Jeśli strona, którą należy zwrócić, jest taka sama jak ta aktualnie przetwarzana, moduł obsługi zdarzeń może po prostu zwrócić wartość null. Tak właśnie dzieje się w linii 3: chcemy zwrócić tę samą stronę [index.xhtml], ale w innym języku.

Wróćmy do architektury przetwarzania żądania:

Menedżer zdarzeń changeLocale.setFrenchLocale został uruchomiony i zwrócił wartość null do kontrolera [Faces Servlet]. Ten z kolei ponownie wyświetli stronę [index.xhtml]. Przyjrzyjmy się jej ponownie:


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

Za każdym razem, gdy obliczana jest wartość typu #{msg['...']}, wykorzystywany jest jeden z plików komunikatów [messages.properties]. Wykorzystywany jest ten, który odpowiada „lokalizacji” strony (wiersz 6). Ponieważ menedżer zdarzeń changeLocale.setFrenchLocale definiuje tę lokalizację jako fr, zostanie użyty plik [messages_fr.properties]. Kliknięcie linku [Anglais] (wiersz 14) spowoduje zmianę lokalizacji na en (por. metoda changeLocale.setEnglishLocale). Wówczas zostanie użyty plik [messages_en.properties], a strona wyświetli się w języku angielskim:

Za każdym razem, gdy wyświetlana jest strona [index.xhtml], wykonywany jest tag <f:view>:


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

a tym samym metoda [ChangeLocale].getLocale() jest ponownie wykonywana. Ponieważ nadaliśmy naszemu beanowi zakres Session:


@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{

lokalizacja ustalona podczas jednego żądania jest zachowywana dla kolejnych żądań.

Pozostaje nam jeszcze jeden element strony [index.xhtml] do przeanalizowania:


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

Tag <h:commandLink> w wierszu 17 ma atrybut „action” o wartości łańcucha znaków. W tym przypadku nie jest wywoływany żaden program obsługi zdarzeń w celu przetworzenia strony. Natychmiast przechodzimy do strony [page1.xhtml]. Przyjrzyjmy się działaniu aplikacji w tym przypadku użycia:

Użytkownik klika link [Page 1]. Formularz jest wysyłany do kontrolera [Faces Servlet]. Kontroler rozpoznaje w otrzymanym żądaniu, że kliknięto link [Page 1]. Sprawdza odpowiedni tag:


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

Z linkiem nie jest powiązany żaden menedżer zdarzeń. Kontroler [Faces Servlet] natychmiast przechodzi do powyższego etapu [3] i wyświetla stronę [page1.xhtml]:

2.4.7. Strona JSF [page1.xhtml]

Strona [page1.xhtml] wysyła do przeglądarki klienta następujący strumień:

 

Kod generujący tę stronę wygląda następująco:


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

Na tej stronie nie ma niczego, czego nie wyjaśniono już wcześniej. Czytelnik sam dopasuje kod JSF do strony wysłanej do przeglądarki klienta. Link powrotny do strony głównej:


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

spowoduje wyświetlenie strony [index.xhtml].

2.4.8. Kompilacja projektu

Nasz projekt jest już gotowy. Możemy go skompilować (Clean and Build):

  • kompilacja projektu tworzy w zakładce [Files] folder [target]. W tym folderze znajduje się archiwum projektu o nazwie [mv-jsf2-02-1.0-SNAPSHOT.war]. To właśnie to archiwum jest wdrażane na serwerze,
  • w [WEB-INF / classes] i [2] znajdują się skompilowane klasy z folderu [Source Packages] projektu, a także pliki, które znajdowały się w gałęzi [Other Sources] – w tym przypadku pliki komunikatów,
  • w [WEB-INF / lib] [3] znajdują się biblioteki projektu,
  • w katalogu głównym [WEB-INF] i [4] znajdują się pliki konfiguracyjne projektu,
  • w katalogu głównym archiwum [5] znajdują się strony JSF, które znajdowały się w gałęzi [Web Pages] projektu,
  • po skompilowaniu projektu można go uruchomić jako [6]. Zostanie on uruchomiony zgodnie z konfiguracją uruchomienia [7],
  • serwer Tomcat zostanie uruchomiony, jeśli nie był jeszcze uruchomiony ([8]),
  • archiwum [mv-jsf2-02-1.0-SNAPSHOT.war] zostanie załadowane na serwer. Nazywa się to wdrożeniem projektu na serwerze aplikacji,
  • w [9] wymagane jest uruchomienie przeglądarki podczas wykonywania. Przeglądarka zażąda kontekstu aplikacji [10], c.a.d. URL, [http://localhost:8080/mv-jsf2-02]. Zgodnie z regułami zawartymi w pliku [web.xml] (patrz strona 44) to plik [faces/index.xhtml] zostanie dostarczony do przeglądarki klienta. Ponieważ plik URL ma postać [/faces/*], zostanie on przetworzony przez kontroler [Faces Servlet] (patrz [web.xml], strona 44). Kontroler ten przetworzy stronę i wyśle następujący strumień HTML:
 
  • Kontroler [Faces Servlet] będzie następnie przetwarzał zdarzenia, które wystąpią na tej stronie.

2.4.9. Plik konfiguracyjny [faces-config.xml]

Wykorzystaliśmy następujący plik [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>
  </application>
</faces-config>

Jest to minimalny plik dla aplikacji JSF 2 z obsługą międzynarodową. Wykorzystaliśmy tutaj nowe możliwości JSF 2 w porównaniu z JSF 1:

  • deklarowanie beanów i ich zakresu za pomocą adnotacji @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
  • przechodzić między stronami, używając jako kluczy nawigacyjnych nazw stron XHTML bez ich rozszerzenia xhtml.

Można zrezygnować z tych możliwości i zadeklarować elementy projektu JSF w pliku [faces-config.xml], tak jak w pliku JSF 1. W takim przypadku plik [faces-config.xml] mógłby wyglądać następująco:


<?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">
<!-- aplikacja -->
  <application>
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
  </application>
  
  <!-- zarządzane komponenty -->
  <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>

   <!-- nawigacja -->
  <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>
  • wiersze 20–24: deklaracja bean’a changeLocale:
    • wiersz 21: nazwa bean;
    • wiersz 22: pełna nazwa klasy powiązanej z beanem;
    • wiersz 23: zakres bean. Możliwe wartości to request, session, application,
  • wiersze 27–34: deklaracja reguły nawigacji:
    • wiersz 28: można opisać regułę. Tutaj tego nie zrobiono;
    • wiersz 29: strona, z której rozpoczyna się nawigacja (punkt wyjścia);
    • wiersze 30–33: przypadek nawigacji. Może ich być kilka;
    • wiersz 31: klucz nawigacyjny;
    • wiersz 32: strona, do której następuje przejście.

Reguły nawigacji można wyświetlić w bardziej przejrzysty sposób. Podczas edycji pliku [faces-config.xml] można skorzystać z zakładki [PageFlow]:

 

Załóżmy, że korzystamy z poprzedniego pliku [faces-config.xml]. Jak zmieniłaby się nasza aplikacja?

  • w klasie [ChangeLocale] adnotacje @ManagedBean i @SessionScoped zniknęłyby, ponieważ teraz bean jest zadeklarowany w pliku [faces-config],
  • Przejście z [index.xhtml] do [page1.xhtml] za pomocą linku wyglądałoby następująco:

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

W atrybucie „action” przypisuje się klucz nawigacyjny p1 zdefiniowany w [faces-config],

  • przejście z [page1.xhtml] do [index.xhtml] za pomocą linku wyglądałoby następująco:

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

Do atrybutu „action” przypisuje się klucz nawigacyjny „welcome” zdefiniowany w [faces-config],

  • metody setFrenchLocale i setEnglishLocale, które powinny zwracać klucz nawigacyjny, nie wymagają modyfikacji, ponieważ zwracały wartość null, wskazując, że użytkownik pozostaje na tej samej stronie.

2.4.10. Wniosek

Wróćmy do projektu NetBeans, który stworzyliśmy:

Projekt ten obejmuje następującą architekturę:

W każdym projekcie JSF znajdziemy następujące elementy:

  • strony JSF [A], które są wysyłane [4] do przeglądarek klientów przez kontroler [Faces Servlet] [3],
  • pliki komunikatów [C], które umożliwiają zmianę języka stron JSF,
  • klasy Java [B], które obsługują zdarzenia występujące w przeglądarce klienckiej [2a, 2b] i/lub służą jako szablony dla stron JSF [3]. Najczęściej warstwy [métier] i [DAO] są opracowywane i testowane oddzielnie. Warstwa [web] jest wówczas testowana z fikcyjną warstwą [métier]. Jeśli dostępne są warstwy [métier] i [DAO], najczęściej pracuje się z ich archiwami .jar.
  • Pliki konfiguracyjne [D] służą do powiązania tych różnych elementów ze sobą. Plik [web.xml] został opisany na stronie 44 i rzadko będzie modyfikowany. To samo dotyczy pliku [faces-config], w przypadku którego zawsze będziemy korzystać z wersji uproszczonej.

2.5. Przykład mv-jsf2-03: formularz wprowadzania danych – komponenty JSF

Od tej pory nie będziemy już pokazywać procesu tworzenia projektu. Przedstawiamy gotowe projekty i wyjaśniamy ich działanie. Czytelnik może pobrać wszystkie przykłady ze strony internetowej niniejszego dokumentu (patrz punkt 1.2).

2.5.1. Aplikacja

Aplikacja posiada jeden widok:

Aplikacja przedstawia główne komponenty JSF, które można wykorzystać w formularzu do wprowadzania danych:

  • kolumna [1] wskazuje nazwę używanego tagu JSF / HTML,
  • kolumna [2] zawiera przykładowe dane dla każdego z napotkanych tagów,
  • kolumna [3] wyświetla wartości komponentu bean służącego jako szablon dla strony,
  • dane wprowadzone w kolumnie [2] są weryfikowane za pomocą przycisku [4]. Weryfikacja ta polega jedynie na aktualizacji bean-a szablonu strony. Następnie zwracana jest ta sama strona. Dlatego po zatwierdzeniu kolumna [3] przedstawia nowe wartości fasoli szablonowej, umożliwiając użytkownikowi sprawdzenie wpływu wprowadzonych danych na szablon strony.

2.5.2. Projekt NetBeans

Projekt NetBeans tej aplikacji wygląda następująco:

  • w [1], pliki konfiguracyjne projektu JSF,
  • w [2], jedyna strona projektu: index.xhtml,
  • w [3], arkusz stylów [styles.css] do konfiguracji wyglądu strony [index.xhtml]
  • w [4] – klasy Java projektu,
  • w [5] – plik komunikatów aplikacji w dwóch językach: francuskim i angielskim.

2.5.3. Plik [pom.xml]

Przedstawiamy jedynie zależności:


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

Są to zależności niezbędne dla projektu JSF. W kolejnych przykładach plik ten będzie przedstawiany tylko wtedy, gdy ulegnie zmianie.

2.5.4. Plik [web.xml]

Plik [web.xml] został skonfigurowany tak, aby strona [index.xhtml] była stroną główną projektu:


<?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>
  • wiersz 30: strona [index.xhtml] jest stroną główną,
  • wiersze 11–14: parametr dla serwletu [Faces Servlet]. Wymaga on, aby komentarze w facelecie o postaci:

        <!-- języki -->

zostały zignorowane. Bez tego parametru komentarze powodują trudne do zrozumienia problemy,

  • wiersze 3–6: parametr dla serwletu [Faces Servlet], który zostanie wyjaśniony nieco dalej.

2.5.5. Plik [faces-config.xml]

Plik [faces-config.xml] aplikacji wygląda następująco:


<?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>
  • wiersze 11–16: konfigurują plik komunikatów aplikacji.

2.5.6. Plik komunikatów [messages.properties]

Pliki komunikatów (patrz [5] na zrzucie ekranu projektu) są następujące:

[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

Komunikaty te są wyświetlane w następujących miejscach na stronie:

Angielska wersja tych komunikatów brzmi następująco:

[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. Szablon [Form.java] strony [index.xhtml]

W powyższym projekcie klasa [Form.java] będzie służyć jako szablon lub backing bean dla stron JSF i [index.xhtml]. Zilustrujmy tę koncepcję szablonu na przykładzie zaczerpniętym ze strony [index.xhtml]:


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

Przy pierwszym wywołaniu strony [index.xhtml] powyższy kod generuje wiersz 2 tabeli danych wprowadzanych:

Wiersz 2 wyświetla pole [1], wiersze 3–6: pole [2], wiersz 7: pole [3].

W wierszach 5 i 7 wykorzystano wyrażenie EL, które odwołuje się do bean formularza zdefiniowanego w klasie [Form.java] w następujący sposób:


package forms;

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


@ManagedBean
@RequestScoped
public class Form {
  • w wierszu 7 zdefiniowano bean bez nazwy. Będzie to zatem nazwa klasy zaczynająca się małą literą: form,
  • bean ma zasięg request. Oznacza to, że w cyklu żądanie klienta / odpowiedź serwera jest on instancjonowany, gdy żądanie tego wymaga, i usuwany po zwróceniu odpowiedzi klientowi.

W poniższym kodzie strony [index.xhtml]:


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

w wierszach 5 i 7 wykorzystywana jest wartość inputText z bean form. Aby zrozumieć powiązania między stroną P a jej modelem M, należy powrócić do cyklu żądanie klienta / odpowiedź serwera, który charakteryzuje aplikację internetową:

Należy odróżnić przypadek, w którym strona P jest wysyłana w odpowiedzi do przeglądarki (etap 4), na przykład podczas pierwszego żądania strony, od przypadku, w którym użytkownik wywołał zdarzenie na stronie P, a zdarzenie to jest przetwarzane przez kontroler [Faces Servlet] (etap 1).

Te dwa przypadki można rozróżnić, patrząc na nie z perspektywy przeglądarki:

  1. podczas pierwszego żądania strony przeglądarka wykonuje operację GET na elemencie URL strony,
  2. podczas przesyłania wartości wprowadzonych na stronie przeglądarka wykonuje operację POST na elemencie URL strony.

W obu przypadkach żądana jest ta sama operacja URL. W zależności od charakteru żądania przeglądarki (GET lub POST) przetwarzanie żądania będzie przebiegało inaczej.

[cas 1 – demande initiale de la page P]

Przeglądarka wysyła żądanie dotyczące strony o identyfikatorze URL, podając identyfikator GET. Kontroler [Faces Servlet] przejdzie bezpośrednio do etapu [4] renderowania odpowiedzi, a strona [index.xhtml] zostanie wysłana do klienta. Kontroler JSF zażąda wyświetlenia każdego elementu strony. Weźmy na przykład wiersz 5 kodu [index.xhtml]:


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

Tag JSF <h:inputText value="wartość"/> generuje tag HTML <input type="text" value="wartość"/>. Klasa odpowiedzialna za przetwarzanie tego tagu napotyka wyrażenie #{form.inputText}, które musi obliczyć:

  • jeśli bean formularza jeszcze nie istnieje, jest on tworzony poprzez instancjonowanie klasy forms.Form,
  • wyrażenie #{form.inputText} jest obliczane poprzez wywołanie metody form.getInputText(),
  • tekst <input id="form:inputText" type="text" name="formulaire:inputText" value="tekst" /> jest wstawiany do strumienia HTML, który zostanie wysłany do klienta, jeśli założymy, że metoda form.getInputText() zwróciła ciąg znaków „tekst”. JSF nada ponadto nazwę (name) komponentowi HTML umieszczonemu w strumieniu. Nazwa ta jest tworzona na podstawie identyfikatorów id analizowanego komponentu JSF oraz identyfikatorów jego komponentów nadrzędnych, w tym przypadku tagu <h:form id="formulaire"/>.

Należy pamiętać, że jeśli na stronie P używa się wyrażenia #{M.champ}, gdzie M jest fasolą modelową strony P, to musi ona dysponować publiczną metodą getChamp(). Typ zwracany przez tę metodę musi dać się przekonwertować na typ String. Możliwym i często spotykanym modelem M jest następujący:

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

gdzie T jest typem, który można przekonwertować na typ String, ewentualnie za pomocą metody toString.

Również w przypadku wyświetlania strony P przetwarzanie wiersza:


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

będzie przebiegać analogicznie i zostanie utworzony następujący strumień HTML:

texte

Wewnętrznie na serwerze strona P jest reprezentowana jako drzewo komponentów, odzwierciedlające drzewo tagów strony wysyłanej do klienta. Drzewo to nazwiemy widokiem lub stanem strony ( ). Stan ten jest zapisywany w pamięci. Może to odbywać się na dwa sposoby, w zależności od konfiguracji określonej w pliku aplikacji [web.xml]:


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

Wiersze 7–11 definiują kontroler [Faces Servlet]. Można go skonfigurować za pomocą różnych tagów <context-param>, w tym tego z wierszy 3–6, który wskazuje, że stan strony ma być zapisany na kliencie (w przeglądarce). Inną możliwą wartością, w wierszu 5, jest „server”, co oznacza zapisanie stanu na serwerze. Jest to wartość domyślna.

Gdy stan strony jest zapisywany po stronie klienta, kontroler JSF dodaje do każdej wysyłanej strony HTML ukryte pole, którego wartością jest aktualny stan strony. Ukryte pole ma następującą postać:

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

Jego wartość przedstawia w postaci zakodowanej stan strony wysłanej do klienta. Należy pamiętać, że to ukryte pole stanowi część formularza strony, a zatem będzie częścią wartości wysyłanych przez przeglądarkę podczas przesyłania formularza. Na podstawie tego ukrytego pola kontroler JSF jest w stanie przywrócić widok w takiej postaci, w jakiej został wysłany do klienta.

Gdy stan strony jest zapisywany na serwerze, stan strony wysłanej do klienta jest zapisywany w sesji klienta. Gdy przeglądarka klienta prześle wartości wprowadzone w formularzu, wyśle również swój token sesji. Na podstawie tego tokenu kontroler JSF odtworzy stan strony wysłanej do klienta i przywróci ją.

Zapisanie stanu strony JSF może wymagać kilkuset bajtów. Ponieważ stan ten jest utrzymywany dla każdego użytkownika aplikacji, w przypadku dużej liczby użytkowników mogą wystąpić problemy z pamięcią. Z tego powodu zdecydowaliśmy się tutaj zapisywać stan strony po stronie klienta (patrz [web.xml], punkt 2.5.4, strona 66).

[cas 2 – traitement de la page P]

Znajdujemy się na powyższym etapie [1], gdzie kontroler [Faces Servlet] otrzyma żądanie POST od przeglądarki klienta, do której wcześniej wysłał stronę [index.xhtml]. Mamy do czynienia z obsługą zdarzenia na stronie. Zanim zdarzenie to będzie mogło zostać obsłużone w [2a], musi nastąpić kilka etapów. Cykl przetwarzania żądania POST przez kontroler JSF wygląda następująco:

FEDCBA

Image

  • w [A], dzięki ukrytemu polu javax.faces.ViewState odtwarzany jest widok pierwotnie wysłany do przeglądarki klienta. W tym momencie elementy strony odzyskują wartości, jakie miały w wysłanej stronie. Nasz komponent inputText odzyskuje wartość „tekst”,
  • w [B] wartości przesłane przez przeglądarkę klienta są wykorzystywane do aktualizacji elementów widoku. Jeśli więc w polu wprowadzania danych o nazwie inputText (HTML) użytkownik wpisał „jean”, wartość „jean” zastępuje wartość „tekst”. Od tej chwili widok odzwierciedla stronę w postaci zmodyfikowanej przez użytkownika, a nie w postaci, w jakiej została wysłana do przeglądarki,
  • w [C] sprawdzane są wartości przesłane. Załóżmy, że poprzedni komponent inputText jest polem do wprowadzania wieku. Wprowadzona wartość musi być liczbą całkowitą. Wartości wysyłane przez przeglądarkę są zawsze typu String. Ich ostateczny typ w modelu M powiązanym ze stroną P może być zupełnie inny. Następuje wówczas konwersja typu String na inny typ T. Konwersja ta może się nie powieść. W takim przypadku cykl żądanie/odpowiedź zostaje zakończony, a strona P zbudowana w formacie [B] jest odsyłana do przeglądarki klienta wraz z komunikatami o błędach, jeśli autor strony P je przewidział. Należy zauważyć, że użytkownik otrzymuje stronę dokładnie taką, jaką wprowadził, bez żadnego wysiłku ze strony programisty. W innej technologii, takiej jak JSP, programista musi samodzielnie odtworzyć stronę P z wartościami wprowadzonymi przez użytkownika. Wartość komponentu może również zostać poddana procesowi walidacji. Wciąż posługując się przykładem komponentu inputText, który jest polem do wprowadzania wieku, wprowadzona wartość musi być nie tylko liczbą całkowitą, ale liczbą całkowitą mieszczącą się w przedziale [1,N]. Jeśli wprowadzona wartość przejdzie etap konwersji, może nie przejść etapu walidacji. W takim przypadku cykl żądanie/odpowiedź również zostaje zakończony, a strona P wygenerowana w [B] jest wysyłana z powrotem do przeglądarki klienta,
  • W przypadku kodu [D], jeśli wszystkie elementy strony P pomyślnie przejdą etap konwersji i walidacji, ich wartości zostaną przypisane do szablonu M strony P. Jeśli wartość pola wprowadzania danych wygenerowanego na podstawie następującego tagu:

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

wynosi „jean”, wówczas wartość ta zostanie przypisana do modelu formularza strony poprzez wykonanie kodu form.setInputText("jean"). Należy pamiętać, że w modelu M strony P prywatne pola modelu M, które przechowują wartość pola wprowadzania danych strony P, muszą posiadać metodę set,

  • gdy model M strony P zostanie zaktualizowany o przesłane wartości, zdarzenie, które wywołało POST strony P, może zostać przetworzone. Jest to etap [E]. Należy zauważyć, że jeśli menedżer tego zdarzenia należy do bean M, ma on dostęp do wartości formularza P, które zostały zapisane w polach tego samego bean.
  • W etapie [E] do kontrolera JSF zostanie zwrócony klucz nawigacyjny. W naszych przykładach będzie to zawsze nazwa strony XHTML, która ma zostać wyświetlona, bez przyrostka .xhtml. Jest to etap [F]. Innym sposobem jest zwrócenie klucza nawigacyjnego, który zostanie wyszukany w pliku [faces-config.xml]. Opisaliśmy ten przypadek.

Z powyższego wynika, że:

  • strona P wyświetla pola C swojego szablonu M za pomocą metod [M].getC(),
  • pola C modelu M strony P są inicjowane wartościami wprowadzonymi na stronie P za pomocą metod [M].setC(wprowadzenie). Na tym etapie mogą wystąpić procesy konwersji i walidacji, które mogą zakończyć się niepowodzeniem. W takim przypadku zdarzenie, które spowodowało wywołanie POST na stronie P, nie jest przetwarzane, a strona jest ponownie wysyłana do klienta w takiej postaci, w jakiej została przez niego wprowadzona.

Szablon [Form.java] dla strony [index.xhtml] będzie wyglądał następująco:


package forms;

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


@ManagedBean
@RequestScoped
public class Form {
  
  /** Tworzy nową instancję formularza */
  public Form() {
  }
  
  // pola formularza
  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";
  
  // zdarzenia
  public String submit(){
    return null;
  }
  
  // metody pobierające i ustawiające
  ...
}

Pola w wierszach 16–27 są wykorzystywane w następujących miejscach formularza:

2.5.8. Strona [index.xhtml]

Strona [index.xhtml], która generuje powyższy widok, wygląda następująco:


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

Omówimy kolejno główne elementy tej strony. Zwróćmy uwagę na ogólną strukturę formularza 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>

Elementy formularza muszą znajdować się wewnątrz tagu <h:form> (wiersze 12–16). Tag <f:view> (wiersze 7–18) jest niezbędny w przypadku internacjonalizacji aplikacji. Ponadto formularz musi posiadać element umożliwiający przesłanie danych (POST), często jest to link lub przycisk, jak w wierszu 14. Dane mogą być również przesyłane w wyniku wielu zdarzeń (zmiana wyboru na liście, zmiana aktywnego pola, wpisanie znaku w polu wprowadzania danych itp.).

2.5.9. Styl formularza

Aby zwiększyć czytelność kolumn tabeli formularza, dołączono do niego arkusz stylów:


  <f:view locale="#{changeLocale.locale}">
    <h:head>
      <title>JSF</title>
      <h:outputStylesheet library="css" name="styles.css"/>
</h:head>
  • wiersz 4: arkusz stylów strony jest zdefiniowany wewnątrz tagu HTML <head> za pomocą tagu:

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

Arkusz stylów będzie wyszukiwany w folderze [resources]:

W tagu:


<h:outputStylesheet library="css" name="styles.css"/>
  • library to nazwa folderu zawierającego arkusz stylów,
  • „name” to nazwa arkusza stylów.

Zobaczmy, jak można wykorzystać ten arkusz stylów:


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

Tag <h:panelGrid columns="3"/> definiuje tabelę z trzema kolumnami. Atrybut columnClasses pozwala nadać styl tym kolumnom. Wartości col1, col2, col3 atrybutu columnClasses określają odpowiednie style kolumn 1, 2 i 3 tabeli. Style te są wyszukiwane w arkuszu stylów strony:


.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
}
  • wiersze 7–9: styl o nazwie col1,
  • wiersze 11–13: styl o nazwie col2,
  • wiersze 15–17: styl o nazwie col3,

Te trzy style określają kolor tła każdej z kolumn.

  • wiersze 19–23: styl „entete” służy do określenia stylu tekstu w pierwszym wierszu tabeli:

          <!-- nagłówki -->
          <h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
          <h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
  • wiersze 1–5: styl „info” służy do określenia stylu tekstów w pierwszej kolumnie tabeli:

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

Nie będziemy się zbytnio rozwodzić nad wykorzystaniem arkuszy stylów, ponieważ temat ten zasługuje na osobną książkę, a ponadto ich tworzenie często powierza się specjalistom. Niemniej jednak postanowiliśmy zastosować jeden, minimalistyczny arkusz, aby przypomnieć, że ich użycie jest niezbędne.

Przyjrzyjmy się teraz, w jaki sposób zdefiniowano obraz tła strony:


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

Obraz tła jest określony za pomocą atrybutu style w tagu <h:body>. Atrybut ten pozwala na określenie elementów stylu. Obraz tła znajduje się w folderze [resources/images/standard.jpg]:

Obraz ten jest pobierany za pomocą plików URL i [/mv-jsf2-03/resources/images/standard.jpg]. Można więc zapisać:


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

/mv-jsf2-03 to kontekst aplikacji. Kontekst ten jest ustalany przez administratora serwera WWW i w związku z tym może ulec zmianie. Kontekst ten można uzyskać za pomocą wyrażenia EL ${request.contextPath}. Dlatego też zaleca się stosowanie następującego atrybutu style:


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

który będzie prawidłowy niezależnie od kontekstu.

2.5.10. Dwa cykle żądania klienta / odpowiedzi serwera dla formularza

Wróćmy do tego, co zostało już wyjaśnione w paragrafie 2.5.7 w ujęciu ogólnym, i zastosujmy to do analizowanego formularza. Zostanie on przetestowany w klasycznym środowisku JSF:

W tym przypadku nie będzie żadnych procedur obsługi zdarzeń ani warstwy [métier]. Etapy [2x] nie będą zatem występować. Należy rozróżnić przypadek, w którym formularz F jest początkowo wywoływany przez przeglądarkę, od przypadku, w którym użytkownik wywołał zdarzenie w formularzu F, a formularz ten jest przetwarzany przez kontroler [Faces Servlet]. Istnieją dwa różne cykle żądania klienta / odpowiedzi serwera.

  • pierwszy, odpowiadający początkowemu żądaniu strony, jest wywoływany przez operację GET przeglądarki na elemencie URL formularza,
  • drugi, odpowiadający przesłaniu wartości wprowadzonych na stronie, jest wywoływany przez operację POST na tej samej operacji URL.

W zależności od rodzaju żądania przeglądarki (GET lub POST) sposób przetwarzania żądania przez kontroler [Faces Servlet] jest różny.

[cas 1 – demande initiale du formulaire F]

Przeglądarka żąda URL strony z GET. Kontroler [Faces Servlet] przejdzie bezpośrednio do etapu [4] renderowania odpowiedzi. Formularz [index.xhtml] zostanie zainicjowany przez swój szablon [Form.java] i wysłany do klienta, który otrzyma następujący widok:

Image

Wymiana danych między klientem a serwerem o numerze HTTP wygląda w tym przypadku następująco:

Żądanie HTTP od klienta:

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

W wierszu 1 widoczny jest komunikat GET z przeglądarki.

Odpowiedź serwera HTTP:

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

Nie pokazano tego tutaj, ale po wierszu 7 następuje pusty wiersz, a następnie kod formularza HTML. To właśnie ten kod przeglądarka interpretuje i wyświetla.

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

Użytkownik wypełnia formularz i zatwierdza go za pomocą przycisku [Valider]. Przeglądarka wysyła wówczas żądanie dotyczące kodu URL formularza wraz z kodem POST. Kontroler [Faces Servlet] przetwarza to żądanie, aktualizuje szablon [Form.java] formularza [index.xhtml], a następnie ponownie wysyła formularz [index.xhtml] zaktualizowany na podstawie tego nowego szablonu. Przyjrzyjmy się temu cyklowi na przykładzie:

Image

Powyżej użytkownik wprowadził dane i je zatwierdził. W odpowiedzi otrzymuje następujący widok:

Image

Wymiana danych między klientem a serwerem o numerze HTTP przebiega w tym przypadku następująco:

Żądanie HTTP od klienta:

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  

W wierszu 1 znajduje się kod POST wygenerowany przez przeglądarkę. W wierszu 14 znajdują się wartości wprowadzone przez użytkownika. Można tam na przykład znaleźć tekst wpisany w polu wprowadzania danych:

formulaire%3AinputText=nouveau+texte

W wierszu 14 przesłano ukryte pole javax.faces.ViewState. Pole to przedstawia w zakodowanej formie stan formularza, jaki został pierwotnie wysłany do przeglądarki podczas jej początkowego GET.

Odpowiedź serwera HTTP:

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

Nie pokazano tego tutaj, ale po wierszu 6 następuje pusty wiersz oraz kod HTML formularza zaktualizowanego na podstawie nowego szablonu pochodzącego z POST.

Przeanalizujmy teraz poszczególne elementy tego formularza.

2.5.11. Tag <h:inputText>

Tag <h:inputText> generuje tag HTML <input type="text" ...>.

Rozważmy następujący kod:


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

oraz jego szablon [Form.java]:


  private String inputText="texte";

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

Gdy strona [index.html] jest wywoływana po raz pierwszy, otrzymujemy następującą stronę:

  • wiersz 2 kodu XHTML generuje [1],
  • tag <h:panelGroup> (wiersze 3–6) pozwala zgrupować kilka elementów w jednej komórce tabeli wygenerowanej przez tag <h:panelGrid> z wiersza 20 pełnego kodu strony (patrz punkt 2.5.8). Tekst [2] jest generowany przez wiersz 4. Pole wprowadzania danych [3] jest generowane przez wiersz [5]. W tym przypadku do wygenerowania tekstu pola wprowadzania danych wykorzystano metodę getInputText z [Form.java] (wiersze 3–5 kodu Java),
  • wiersz 7 kodu XHTML generuje [4]. Ponownie wykorzystano metodę getInputText z pliku [Form.java] do wygenerowania tekstu [4].

Strumień HTML wygenerowany przez stronę XHTML wygląda następująco:


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

Tagi HTML <tr> i <td> są generowane przez tag <h:panelGrid>, który służy do generowania tabeli formularza.

Teraz poniżej wprowadźmy wartość w polu wprowadzania danych [1] i zatwierdźmy formularz za pomocą przycisku [Valider] [2]. W odpowiedzi otrzymujemy stronę [3, 4]:

Wartość pola [1] jest przesyłana w następujący sposób:

formulaire%3AinputText=nouveau+texte

W polu [2] formularz jest zatwierdzany za pomocą następującego przycisku:


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

Tag <h:commandButton> nie posiada atrybutu action. W takim przypadku nie jest wywoływany żaden handler zdarzeń ani żadna reguła nawigacji. Po przetworzeniu zwracana jest ta sama strona. Przyjrzyjmy się ponownie cyklowi jej przetwarzania:

ABCDEF

Image

  • w [A] strona P jest przywracana w takiej postaci, w jakiej została wysłana. Oznacza to, że komponent o identyfikatorze inputText jest przywracany wraz ze swoją początkową wartością „tekst”,
  • w [B] wartości przesłane przez przeglądarkę (wprowadzone przez użytkownika) są przypisywane do komponentów strony P. Tutaj komponent o identyfikatorze inputText otrzymuje wartość „nowy tekst”,
  • w [C] odbywają się konwersje i walidacje. W tym przypadku nie ma żadnych. W szablonie M pole powiązane z komponentem o identyfikatorze inputText jest następujące:

private String inputText="texte";

Ponieważ wprowadzone wartości są typu String, nie ma potrzeby przeprowadzania konwersji. Ponadto nie utworzono żadnych reguł walidacji. Stworzymy je w późniejszym terminie.

  • W przypadku [D] wprowadzone wartości są przypisywane do szablonu. Pole inputText w [Form.java] otrzymuje wartość „nowy tekst”,
  • w [E] nie dzieje się nic, ponieważ do przycisku [Valider] nie przypisano żadnego obsługi zdarzeń.
  • W [F] strona P jest ponownie wysyłana do klienta, ponieważ przycisk [Valider] nie ma atrybutu action. Następnie wykonywane są kolejne wiersze z pliku [index.xhtml]:

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

W wierszach 5 i 7 wykorzystano wartość pola inputText z szablonu, która obecnie brzmi „nowy tekst”. Stąd uzyskany wynik:

Image

2.5.12. Tag <h:inputSecret>

Tag <h:inputSecret> generuje tag HTML <input type="password" ...>. Jest to pole wprowadzania danych podobne do tego z tagu JSF <h:inputText>, z tą różnicą, że każdy znak wpisany przez użytkownika jest wizualnie zastępowany znakiem *.

Rozważmy następujący kod:


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

oraz jego szablon w [Form.java]:


private String inputSecret="secret";

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następujący wynik:

  • wiersz 2 kodu XHTML generuje [1]
  • tekst [2] jest generowany przez wiersz 4. Pole wprowadzania danych [3] jest generowane przez wiersz [5]. Zazwyczaj do wygenerowania tekstu pola wprowadzania danych powinna zostać użyta metoda getInputSecret z pliku [Form.java]. Wyjątek stanowi sytuacja, gdy pole to jest typu „hasło”. Tag <h:inputSecret> służy wyłącznie do odczytu wprowadzonych danych, a nie do ich wyświetlania.
  • Wiersz 7 kodu XHTML generuje [4]. W tym przypadku do wygenerowania tekstu [4] wykorzystano metodę getInputSecret z klasy [Form.java] (patrz wiersz 1 kodu Java).

Strumień HTML wygenerowany przez stronę XHTML wygląda następująco:


<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>
  • wiersz 3: tag HTML <input type= "password " .../> wygenerowany przez tag JSF <h:inputSecret>

Teraz poniżej wprowadźmy wartość w polu wprowadzania danych [1] i zatwierdźmy formularz za pomocą przycisku [Valider] [2]. W odpowiedzi otrzymujemy stronę [3]:

Wartość pola [1] jest wysyłana w następujący sposób:

formulaire%3AinputSecret=mdp

Walidacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Pole inputSecret w szablonie [Form.java] otrzymało wówczas wartość „mdp”. Ponieważ formularz [index.xhtml] nie zdefiniował żadnych reguł nawigacji ani żadnych obsługi zdarzeń, po aktualizacji jego szablonu jest on ponownie wyświetlany. W ten sposób powracamy do widoku z pierwotnego wywołania strony [index.xhtml], gdzie po prostu wartość pola inputSecret w szablonie zmieniła się z [3].

2.5.13. Tag <h:inputTextArea>

Tag <h:inputTextArea> generuje tag HTML <textarea ...>tekst</textarea>. Jest to pole wprowadzania danych podobne do tego z tagu JSF <h:inputText>, z tą różnicą, że tutaj można wpisać kilka wierszy tekstu.

Rozważmy następujący kod:


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

oraz jego szablon w [Form.java]:


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

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następujący wynik:

  • wiersz 2 kodu XHTML generuje [1],
  • tekst [2] jest generowany przez wiersz 4. Pole wprowadzania danych [3] jest generowane przez wiersz [5]. Jego zawartość została wygenerowana poprzez wywołanie metody getInputTextArea modelu, która zwróciła wartość zdefiniowaną w wierszu 1 powyższego kodu Java,
  • wiersz 7 kodu XHTML generuje [4]. Tutaj ponownie wykorzystano metodę getInputTextArea z [Form.java]. Ciąg znaków „linia1\nlinia2” zawierał znaki końca linii \n. Nadal tam są. Jednak po wstawieniu do strumienia HTML są one wyświetlane przez przeglądarki jako spacje. Z kolei tag HTML <textarea>, który wyświetla [3], poprawnie interpretuje znaki końca linii.

Strumień HTML wygenerowany przez stronę XHTML wygląda następująco:


<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>
  • wiersze 3–5: tag HTML <textarea>...</textarea> wygenerowana przez tag JSF <h:inputTextArea>

Teraz poniżej wprowadźmy wartość w polu wprowadzania danych [1] i zatwierdźmy formularz za pomocą przycisku [Valider] [2]. W odpowiedzi otrzymujemy stronę [3]:

Wartość przesłanego pola [1] jest następująca:

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

Walidacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Pole textArea w szablonie [Form.java] otrzymało wówczas wartość „Samouczek JSF\nczęść1”. Ponowne wyświetlenie [index.xhtml] pokazuje, że pole textArea w szablonie zostało rzeczywiście zaktualizowane do wartości [3].

2.5.14. Tag <h:selectOneListBox>

Tag <h:selectOneListBox> generuje tag HTML <select>...</select>. Wizualnie tworzy on listę rozwijaną lub listę z suwakiem.

Rozważmy następujący kod:


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

oraz jego szablon w [Form.java]:


private String selectOneListBox1="2";

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następujący wynik:

  • wiersz 2 kodu XHTML generuje [1]
  • tekst [2] jest generowany przez wiersz 4. Lista rozwijana [3] jest generowana przez wiersze [5-9]. To właśnie wartość atrybutu size="1" sprawia, że lista wyświetla tylko jeden element. Jeśli ten atrybut nie występuje, domyślną wartością atrybutu size jest 1. Elementy listy zostały wygenerowane przez tagi <f:selectItem> w wierszach 6–8. Tagi te mają następującą składnię:

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

Wartość atrybutu itemLabel odpowiada temu, co jest wyświetlane na liście. Wartość atrybutu itemValue stanowi wartość elementu. To właśnie ta wartość zostanie przesłana do kontrolera [Faces Servlet], jeśli element zostanie wybrany z listy rozwijanej.

Element wyświetlany w [3] został określony poprzez wywołanie metody getSelectOneListBox1() (wiersz 5). Uzyskany wynik „2” (wiersz 1 kodu Java) spowodował wyświetlenie elementu z wiersza 7 listy rozwijanej, ponieważ jego atrybut itemValue ma wartość „2”,

  • wiersz 11 kodu XHTML generuje [4]. W tym przypadku ponownie wykorzystano metodę getSelectOneListBox1 z [Form.java].

Przepływ HTML wygenerowany przez stronę XHTML wygląda następująco:


<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>
  • wiersze 3 i 7: tag HTML <select ...>...</select> wygenerowany przez tag JSF <h:selectOneListBox>,
  • wiersze 4–6: tagi HTML <option ...> ... </option> wygenerowane przez tagi JSF <f:selectItem>,
  • wiersz 5: fakt, że element o wartości="2" jest zaznaczony na liście, przekłada się na obecność atrybutu selected="selected".

Teraz, poniżej, wybierzmy [1] nową wartość z listy i zatwierdźmy formularz za pomocą przycisku [Valider] [2]. W odpowiedzi otrzymujemy stronę [3]:

Wartość przesłanego pola [1] jest następująca:

formulaire%3AselectOneListBox1=3

Walidacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Element HTML


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

został wybrany. Przeglądarka wysłała ciąg znaków „3” jako wartość komponentu JSF, który wygenerował listę rozwijaną:


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

Kontroler JSF użyje metody setSelectOneListBox1("3") w celu zaktualizowania modelu listy rozwijanej. Po tej aktualizacji pole modelu [Form.java]


        private String selectOneListBox1;

zawiera teraz wartość „3”.

Gdy strona [index.xhtml] zostanie ponownie wyświetlona po zakończeniu przetwarzania, wartość ta powoduje wyświetlenie powyższego elementu [3,4]:

  • określa ona element listy rozwijanej, który ma zostać wyświetlony – [3],
  • wartość pola selectOneListBox1 jest wyświetlana w [4].

Rozważmy wariant znacznika <h:selectOneListBox>:


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

Szablon w [Form.java] dla znacznika <h:selectOneListBox> w wierszu 5 wygląda następująco:


  private String selectOneListBox2="3";

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następującą stronę:

  • wiersz 2 kodu XHTML generuje [1],
  • tekst [2] jest generowany przez wiersz 4. Lista z paskiem przewijania [3] jest generowana przez wiersze [5-11]. To właśnie wartość atrybutu size= „3” sprawia, że mamy listę z suwakiem, a nie listę rozwijaną. Elementy listy zostały wygenerowane przez tagi <f:selectItem> w wierszach 6–8,

Element wybrany w [3] został określony poprzez wywołanie metody getSelectOneListBox2() (wiersz 5). Uzyskany wynik „3” (wiersz 1 kodu Java) spowodował wyświetlenie elementu z wiersza 8 listy, ponieważ jego atrybut itemValue ma wartość „3”,

  • wiersz 13 kodu XHTML generuje [4]. W tym przypadku ponownie wykorzystano metodę getSelectOneListBox2 z [Form.java].

Przepływ HTML wygenerowany przez stronę XHTML wygląda następująco:


<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>
  • wiersz 6: fakt, że element o wartości="3" został zaznaczony na liście, przekłada się na obecność atrybutu selected="selected".

Teraz, poniżej, wybierzmy [1] nową wartość z listy i zatwierdźmy formularz za pomocą przycisku [Valider] [2]. W odpowiedzi otrzymujemy stronę [3]:

Wartość przesłana dla pola [1] jest następująca:

formulaire%3AselectOneListBox2=5

Walidacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Element HTML


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

został wybrany. Przeglądarka wysłała ciąg znaków „5” jako wartość komponentu JSF, który wygenerował listę rozwijaną:


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

Kontroler JSF użyje metody setSelectOneListBox2("5") w celu zaktualizowania szablonu listy. Po tej aktualizacji pole


        private String selectOneListBox2;

zawiera teraz wartość „5”.

Gdy strona [index.xhtml] zostanie ponownie wyświetlona po zakończeniu przetwarzania, wartość ta powoduje wyświetlenie powyższego elementu [3,4]:

  • określa ona element listy, który ma zostać zaznaczony – [3],
  • wartość pola selectOneListBox2 jest wyświetlana jako [4].

2.5.15. Tag <h:selectManyListBox>

Tag <h:selectmanyListBox> generuje tag <select multiple="multiple">...</select>, który pozwala użytkownikowi wybrać wiele elementów z listy.

Rozważmy następujący kod:


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

oraz jego szablon w [Form.java]:


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

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następujący wynik:

  • wiersz 2 kodu XHTML generuje [1]
  • tekst [2] jest generowany przez wiersz 4. Lista [3] jest generowana przez wiersze [5-11]. Atrybut size="3" powoduje, że lista wyświetla w danym momencie trzy z tych elementów. Elementy wybrane z listy zostały określone poprzez wywołanie metody getSelectManyListBox() (wiersz 5) w modelu Java. Uzyskany wynik {"1","3"} (wiersz 1 kodu Java) jest tablicą elementów typu String. Każdy z tych elementów służy do zaznaczenia jednego z elementów listy. W tym przypadku zostaną zaznaczone elementy z wierszy 6 i 10, których atrybut itemValue znajduje się w tablicy {"1","3"}. Pokazuje to [3].
  • Wiersz 14 kodu XHTML generuje [4]. W tym przypadku wywoływana jest nie metoda getSelectManyListBox z modelu Java listy, ale następująca metoda getSelectManyListBoxValue:

private String[] selectManyListBox=new String[]{"1","3"};
  ...
  // metody pobierające i ustawiające
  
  public String getSelectManyListBoxValue(){
    return getValue(selectManyListBox);
  }
  
  private String getValue(String[] chaines){
    String value="[";
    for(String chaine : chaines){
      value+=" "+chaine;
    }
    return value+"]";
  }

Gdyby wywołano metodę getSelectManyListBox, uzyskano by tablicę String. Aby włączyć ten element do strumienia HTML, kontroler musiałby wywołać swoją metodę toString. Jednak ta metoda, w przypadku tablicy, zwraca jedynie jej „hashcode”, a nie listę jej elementów, jak tego oczekujemy. Dlatego też wykorzystujemy powyższą metodę getSelectManyListBoxValue, aby uzyskać ciąg znaków reprezentujący zawartość tablicy,

  • linia 12 kodu XHTML generuje przycisk [5]. Po kliknięciu tego przycisku wykonywany jest kod JavaScript zawarty w atrybucie onclick. Zostanie on osadzony na stronie HTML, która zostanie wygenerowana przez kod JSF. Aby to zrozumieć, musimy poznać dokładny charakter tej strony.

Przepływ HTML wygenerowany przez stronę XHTML wygląda następująco:


<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>
  • wiersze 3 i 9: tag HTML <select multiple="multiple"...>...</select> wygenerowany przez tag JSF <h:selectManyListBox>. To właśnie obecność atrybutu multiple wskazuje, że mamy do czynienia z listą umożliwiającą wielokrotny wybór,
  • a fakt, że modelem listy jest tablica String {"1","3"}, sprawia, że elementy listy w wierszach 4 (value="1") i 6 (value="3") mają atrybut selected="selected",
  • wiersz 10: po kliknięciu przycisku [Raz] wykonywany jest kod JavaScript zawarty w atrybucie onclick. Strona jest reprezentowana w przeglądarce przez drzewo obiektów, często nazywane DOM (Document Object Model). Każdy obiekt drzewa jest dostępny dla kodu JavaScript poprzez swój atrybut name. Lista w wierszu 3 powyższego kodu HTML nosi nazwę formularz:selectManyListBox. Sam formularz można określać na różne sposoby. W tym przypadku jest on oznaczony notacją this.form, gdzie this oznacza przycisk [Raz], a this.form – formularz, w którym znajduje się ten przycisk. Lista formularz:selectManyListBox znajduje się w tym samym formularzu. Zatem zapis this.form['formulaire:selectManyListBox'] wskazuje położenie listy w drzewie komponentów formularza. Obiekt reprezentujący listę posiada atrybut selectedIndex, którego wartością jest numer elementu wybranego na liście. Numer ten zaczyna się od 0, co oznacza pierwszy element listy. Wartość -1 oznacza, że żaden element nie jest zaznaczony na liście. Kod JavaScript przypisujący wartość -1 do atrybutu selectedIndex powoduje odznaczenie wszystkich elementów listy, o ile takie istnieją.

Teraz poniżej wybierzmy nowe wartości z listy (aby zaznaczyć wiele elementów z listy, należy przytrzymać klawisz Ctrl podczas klikania) i zatwierdźmy formularz za pomocą przycisku. W odpowiedzi otrzymujemy stronę:

Wartość przesłanego pola [1] jest następująca:

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

Walidacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Elementy HTML


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

zostały wybrane. Przeglądarka przesłała trzy ciągi znaków „3”, „4”, „5” jako wartości komponentu JSF, który wygenerował listę rozwijaną:


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

Metoda setSelectManyListBox szablonu zostanie wykorzystana do aktualizacji tego szablonu o wartości przesłane przez przeglądarkę:


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

W wierszu 3 widać, że parametrem metody jest tablica o nazwie String. W tym przypadku będzie to tablica {"3","4","5"}. Po tej aktualizacji pole


        private String[] selectManyListBox;

zawiera teraz tablicę {"3","4","5"}.

Gdy strona [index.xhtml] zostanie ponownie wyświetlona po zakończeniu przetwarzania, wartość ta powoduje wyświetlenie powyższego [3,4]:

  • określa ona elementy listy, które mają zostać zaznaczone ([3]),
  • wartość pola selectManyListBox jest wyświetlana jako [4].

2.5.16. Tag <h:selectOneMenu>

Tag <h:selectOneMenu> jest identyczny z tagiem <h:selectOneListBox size="1">. W przykładzie wykonywany kod JSF wygląda następująco:


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

Szablon znacznika <h:selectOneMenu> w [Form.java] wygląda następująco:


  private String selectOneMenu="1";

Przy pierwszym wywołaniu strony [index.xhtml] powyższy kod generuje następujący widok:

Przykładowe wykonanie mogłoby wyglądać następująco:

Wartość przesłana dla pola [1] jest następująca:

formulaire%3AselectOneMenu=4

2.5.17. Tag <h:selectManyMenu>

Tag <h:selectManyMenu> jest identyczny z tagiem <h:selectManyListBox size="1">. Kod JSF wykonany w przykładzie wygląda następująco:


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

Szablon znacznika <h:selectManyMenu> w [Form.java] wygląda następująco:


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

Na podstawie pierwotnego zapytania dotyczącego strony [index.xhtml] powyższy kod generuje stronę:

Lista [1] zawiera teksty „jeden”, …, „pięć”, przy czym zaznaczone są elementy „jeden” i „dwa”. Wygenerowany kod HTML wygląda następująco:


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

Jak widać powyżej, w wierszach 4 i 5 zaznaczone są elementy „un” i „deux” (obecność atrybutu selected).

Trudno jest przedstawić zrzut ekranu z przykładowym wynikiem, ponieważ nie można pokazać elementów zaznaczonych w menu. Zachęcamy czytelnika do samodzielnego przeprowadzenia testu (aby zaznaczyć kilka elementów na liście, należy przytrzymać klawisz Ctrl podczas klikania).

2.5.18. Tag <h:inputHidden>

Tag <h:inputHidden> nie ma wizualnego odpowiednika. Służy wyłącznie do wstawienia tagu <input type="hidden" value="..."/> do strumienia strony. Umieszczone wewnątrz tagu <h:form>, ich wartości stanowią część danych wysyłanych do serwera po przesłaniu formularza. Ponieważ są to pola formularza, których użytkownik nie widzi, nazywane są polami ukrytymi. Zaletą tych pól jest zachowanie danych w pamięci między kolejnymi cyklami żądania/odpowiedzi tego samego klienta:

  • klient żąda formularza F. Serwer wysyła mu go i umieszcza informację I w ukrytym polu C w postaci <h:inputHidden id="C" value="I"/>,
  • gdy klient wypełni formularz F i prześle go do serwera, wartość I z pola C jest odsyłana do serwera. Serwer może wówczas odzyskać informację I, którą zapisał na stronie. W ten sposób stworzono pamięć pomiędzy dwoma cyklami żądania i odpowiedzi,
  • JSF sam wykorzystuje tę technikę. Informacja I, którą przechowuje w formularzu F, to wartość wszystkich jego komponentów. Wykorzystuje do tego następujące pole ukryte:

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

Ukryte pole nosi nazwę javax.faces.ViewState, a jego wartość to ciąg znaków, który w zakodowanej postaci reprezentuje wartości wszystkich elementów strony wysłanej do klienta. Gdy klient odsyła stronę po wprowadzeniu danych w formularzu, ukryte pole javax.faces.ViewState jest odsyłane wraz z wprowadzonymi wartościami. To właśnie pozwala kontrolerowi JSF odtworzyć stronę taką, jaka została pierwotnie wysłana. Mechanizm ten został wyjaśniony na stronie 72.

Kod JSF z przykładu wygląda następująco:


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

Szablon tagu <h:inputHidden> w [Form.java] wygląda następująco:


  private String inputHidden="initial";

W rezultacie podczas pierwszego wywołania strony [index.xhtml] wyświetla się następujący wynik:

  • wiersz 2 generuje [1], a wiersz 4 – [2]. Wiersz 3 nie generuje żadnego elementu wizualnego.

Wygenerowany kod HTML wygląda następująco:


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

W momencie przesłania formularza o kodzie POST „początkowa” wartość pola o nazwie formularz:inputHidden z wiersza 3 zostanie przesłana wraz z pozostałymi wartościami formularza. Pole


  private String inputHidden;

zostanie zaktualizowane o tę wartość, która jest tą samą, jaką miało już początkowo. Wartość ta zostanie uwzględniona w nowej stronie wysyłanej do klienta. W rezultacie zawsze otrzymujemy zrzut ekranu przedstawiony powyżej.

Wartość przesłana dla pola ukrytego jest następująca:

formulaire%3AinputHidden=initial

2.5.19. Tag <h:selectBooleanCheckBox>

Tag <h:selectBooleanCheckBox> generuje tag HTML <input type="checkbox" ...>.

Rozważmy następujący kod JSF:


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

Szablon tagu <h:selectBooleanCheckbox> z linii 5 powyżej w pliku [Form.java] wygląda następująco:


  private boolean selectBooleanCheckbox=true;

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następującą stronę:

  • wiersz 2 kodu XHTML generuje [1],
  • tekst [2] jest generowany przez wiersz 4. Pole wyboru [3] jest generowane przez wiersz [5]. W tym przypadku do zaznaczenia lub odznaczenia pola wyboru wykorzystano metodę getSelectBooleanCheckbox z pliku [Form.java]. Ponieważ metoda ta zwraca wartość logiczną true (patrz kod Java), pole wyboru zostało zaznaczone,
  • wiersz 7 kodu XHTML generuje [4]. Ponownie wykorzystano metodę getSelectBooleanCheckbox z pliku [Form.java] do wygenerowania tekstu [4].

Strumień HTML wygenerowany przez poprzedni kod JSF wygląda następująco:


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

W pliku [4] widoczny jest wygenerowany tag HTML <input type="checkbox">. Wartość true z powiązanego szablonu spowodowała, że do tagu dodano atrybut checked="checked". Dzięki temu pole wyboru jest zaznaczone.

Teraz poniżej odznaczmy pole wyboru [1], prześlijmy formularz [2] i przyjrzyjmy się uzyskanym wynikom [3, 4]:

Ponieważ pole wyboru jest odznaczone, nie ma wartości wprowadzonej dla pola [1].

Weryfikacja formularza przez [2] spowodowała aktualizację szablonu [Form.java] poprzez wpis [1]. Pole selectBooleanCheckbox w szablonie [Form.java] otrzymało wówczas wartość „false”. Ponowne wyświetlenie [index.xhtml] pokazuje, że pole selectBooleanCheckbox w modelu zostało rzeczywiście zaktualizowane do wartości [3] i [4]. Warto tutaj zauważyć, że to właśnie dzięki ukrytemu polu javax.faces.ViewState pole JSF było w stanie stwierdzić, że pole wyboru, które było początkowo zaznaczone, zostało odznaczone przez użytkownika. Wartość odznaczonego pola wyboru nie jest bowiem częścią wartości wysyłanych przez przeglądarkę. Dzięki drzewu komponentów przechowywanemu w ukrytym polu javax.faces.ViewState, JSF ustala, że w formularzu znajdowało się pole wyboru o nazwie „selectBooleanCheckbox”, a jego wartość nie znajduje się wśród wartości przesłanych przez przeglądarkę klienta. Może na tej podstawie wywnioskować, że w przesłanym formularzu pole to było odznaczone, co pozwala mu przypisać wartość logiczną false do powiązanego modelu Java:


  private boolean selectBooleanCheckbox;

2.5.20. Tag <h:selectManyCheckBox>

Tag <h:selectManyCheckBox> generuje grupę pól wyboru, a tym samym kilka tagów HTML <input type="checkbox" ...>. Tag ten jest odpowiednikiem tagu <h:selectManyListBox>, z tą różnicą, że elementy do zaznaczenia są przedstawione w postaci przylegających do siebie pól wyboru, a nie w formie listy. To, co powiedziano o tagu <h:selectManyListBox>, ma tutaj zastosowanie.

Rozważmy następujący kod JSF:


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

Szablon tagu <h:selectManyCheckbox> z linii 5 powyżej w pliku [Form.java] wygląda następująco:


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

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, otrzymujemy następującą stronę:

  • wiersz 2 kodu XHTML generuje [1],
  • tekst [2] jest generowany przez wiersz 4. Pola wyboru [3] są generowane przez wiersze 5–10. Dla każdego z nich:
  • atrybut itemLabel określa tekst wyświetlany obok pola wyboru;
  • atrybut „itemvalue” określa wartość, która zostanie przesłana na serwer, jeśli pole zostanie zaznaczone;

Szablonem dla tych czterech pól wyboru jest następujące pole Java:


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

Ta tabela określa:

  • które pola wyboru mają być zaznaczone po wyświetleniu strony. Odbywa się to poprzez ich wartość, c.a.d, oraz ich pole itemValue. W powyższym przykładzie pola wyboru, których wartości znajdują się w tablicy {"1", "3"}, zostaną zaznaczone. Tak właśnie wygląda to na powyższym zrzucie ekranu;
  • gdy strona zostanie wysłana, szablon selectManyCheckbox otrzymuje tablicę wartości pól wyboru zaznaczonych przez użytkownika. To właśnie zobaczymy za chwilę,
  • wiersz 12 kodu XHTML generuje [4]. To właśnie następująca metoda getSelectManyCheckboxValue wygenerowała [4]:

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

Przepływ HTML wygenerowany przez poprzedni kod JSF jest następujący:


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

Wygenerowano cztery tagi HTML <input type="checkbox" ...>. Tagi w wierszach 3 i 7 mają atrybut checked="checked", co powoduje, że są one zaznaczone. Należy zauważyć, że wszystkie mają ten sam atrybut name="formulaire:selectManyCheckbox", czyli wszystkie cztery pola HTML mają tę samą nazwę. Jeśli użytkownik zaznaczy pola wyboru w wierszach 5 i 9, przeglądarka wyśle wartości tych czterech pól wyboru w następującej postaci:

formulaire:selectManyCheckbox=2&formulaire:selectManyCheckbox=4

a szablon czterech pól


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

otrzyma tablicę {"2","4"}.

Sprawdźmy to poniżej. W [1] wprowadzamy zmianę, w [2] zatwierdzamy formularz. W [3] otrzymujemy następujący wynik:

Wartości przesłane dla pól [1] są następujące:

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

2.5.21. Tag <h:selectOneRadio>

Tag <h:selectOneRadio> generuje grupę przycisków opcji, które wykluczają się nawzajem.

Rozważmy następujący kod JSF:


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

Szablon tagu <h:selectOneRadio> z linii 5 powyżej wygląda następująco w [Form.java]:


  private String selectOneRadio="2";

Gdy strona [index.xhtml] jest wywoływana po raz pierwszy, uzyskany widok wygląda następująco:

  • wiersz 2 kodu XHTML generuje [1],
  • Tekst [2] jest generowany przez wiersz 4. Przyciski opcji [3] są generowane przez wiersze 5–10. Dla każdego z nich:
  • atrybut itemLabel określa tekst wyświetlany obok przycisku opcji;
  • atrybut „itemvalue” określa wartość, która zostanie przesłana na serwer, jeśli przycisk zostanie zaznaczony;

Szablonem dla czterech przycisków opcji jest następujące pole Java:


  private String selectOneRadio="2";

Ten szablon określa:

  • który przycisk wyboru ma być zaznaczony po wyświetleniu strony. Odbywa się to poprzez jego wartość, c.a.d, oraz pole itemValue. W powyższym przykładzie zaznaczony zostanie przycisk opcji o wartości „2”. Widać to na powyższym zrzucie ekranu;
  • gdy strona zostanie wysłana, szablon selectOneRadio otrzymuje wartość przycisku opcji, który został zaznaczony. To właśnie zobaczymy za chwilę,
  • wiersz 12 kodu XHTML generuje plik [4].

Strumień HTML wygenerowany przez poprzedni kod JSF wygląda następująco:


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

Wygenerowano cztery tagi HTML <input type="radio" ...>. Tag w wierszu 8 posiada atrybut checked="checked", co powoduje, że odpowiadający mu przycisk opcji jest zaznaczony. Należy zauważyć, że wszystkie tagi mają ten sam atrybut name="formularz:selectOneRadio", czyli wszystkie cztery pola HTML mają tę samą nazwę. Jest to warunek konieczny do utworzenia grupy przycisków opcji wykluczających się: gdy jeden jest zaznaczony, pozostałe nie są.

Poniżej, w polu [1] zaznaczamy jeden z przycisków opcji, w polu [2] przesyłamy formularz, a w polu [3] widoczny jest uzyskany wynik:

Wartość przesłana dla pola [1] jest następująca:

formulaire%3AselectOneRadio=4

2.6. Przykład mv-jsf2-04: listy dynamiczne

2.6.1. Aplikacja

Aplikacja jest taka sama jak poprzednio:

Jedyne zmiany dotyczą sposobu generowania elementów list w obszarach [1] i [2]. Są one tutaj generowane dynamicznie przez kod Java, podczas gdy w poprzedniej wersji były zapisane „na stałe” w kodzie strony JSF.

2.6.2. Projekt NetBeans

Projekt NetBeans tej aplikacji wygląda następująco:

Projekt [mv-jsf2-04] jest identyczny z projektem [mv-jsf2-03], z wyjątkiem następujących różnic:

  • w projekcie [1], na stronie JSF elementy list nie będą już zapisywane „na stałe” w kodzie,
  • w projekcie [2] szablon strony JSF i [1] zostanie zmodyfikowany,
  • w [3] zostanie zmieniony jeden z komunikatów.

2.6.3. Strona [index.xhtml] i jej szablon [Form.java]

Strona JSF [index.xhtml] przyjmuje następujący kształt:


<?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">
        <!-- języki -->
        <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">
...
          <!-- wiersz 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}"/>
          <!-- wiersz 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}"/>
          <!-- wiersz 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}"/>
          <!-- wiersz 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}"/>
          <!-- wiersz 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"/>
...
          <!-- wiersz 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}"/>
          <!-- wiersz 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>

Wprowadzone zmiany pokazano w wierszach 26–28. Tam, gdzie wcześniej znajdował się kod:


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

teraz mamy ten:


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

Trzy tagi <f:selectItem> z wierszy 2–4 zostały zastąpione pojedynczym tagiem <f:selectItems> z wiersza b. Ten tag posiada atrybut „value”, którego wartością jest zbiór elementów typu javax.faces.model.SelectItem. Powyżej wartość atrybutu „value” zostanie uzyskana poprzez wywołanie następującej metody [form].getSelectOneListbox1Items:


  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;
}
  • W wierszu 1 metoda getSelectOneListbox1Items zwraca tablicę elementów typu javax.faces.model.SelectItem utworzoną przez prywatną metodę getItems z wiersza 5. Należy zauważyć, że metoda getSelectOneListbox1Items nie jest metodą getter dla pola prywatnego selectOneListBox1Items,
  • klasa javax.faces.model.SelectItem posiada różne konstruktory.

Image

Wykorzystujemy wiersz 8 metody getItems, konstruktor SelectItem(Object value, String label), który odpowiada znacznikowi JSF


    <f:selectItem itemValue="value" labelValue="label"/>
  • wiersze 5–10: metoda getItems(String label, int qte) tworzy tablicę zawierającą qte elementów typu SelectItem, gdzie element i jest uzyskiwany za pomocą konstruktora SelectItem(i, label+i).

Kod JSF


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

staje się wówczas funkcjonalnie równoważny z następującym kodem 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>

To samo dotyczy wszystkich pozostałych list na stronie JSF. W szablonie [Form.java] znajdują się zatem następujące nowe metody:


  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. Plik komunikatów

Zmieniono tylko jedną wiadomość:

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

Zachęcamy czytelników do przetestowania tej nowej wersji.

Najczęściej dynamiczne elementy formularza są wynikiem przetwarzania biznesowego lub pochodzą z bazy danych:

Przeanalizujmy początkowe żądanie strony JSF [index.xhtml] wysłane przez przeglądarkę za pomocą GET:

  • strona JSF jest wywoływana przez [1],
  • kontroler [Faces Servlet] zleca jej wyświetlenie w [3]. Silnik JSF, który przetwarza stronę, wywołuje jej szablon [Form.java], na przykład metodę getSelectOneListBox1Items. Metoda ta mogłaby z powodzeniem zwrócić tablicę elementów typu SelectItem na podstawie informacji zapisanych w bazie danych. W tym celu wykorzystałaby warstwę [métier] [2b].

2.7. Przykład mv-jsf2-05: nawigacja – sesja – obsługa wyjątków

2.7.1. Aplikacja

Aplikacja jest taka sama jak poprzednio, z tą różnicą, że formularz ma teraz postać wielostronicowego kreatora:

  • w [1], strona 1 formularza – można do niej przejść również poprzez link 1 w [2]
  • w [2], grupa 5 linków.
  • w [3], strona 2 formularza, do której prowadzi link 2 z [2]
  • w [4], strona 3 formularza, do której prowadzi link 3 z [2]
  • do [5], strona wyświetlana po kliknięciu linku „Wywołaj wyjątek” w [2]
  • w [6] – strona wyświetlana po kliknięciu linku 4 w [2]. Zawiera podsumowanie danych wprowadzonych na stronach od 1 do 3.

2.7.2. Projekt NetBeans

Projekt NetBeans tej aplikacji wygląda następująco:

Projekt [mv-jsf2-05] wprowadza dwie nowości:

  1. w [1] strona JSF [index.xhtml] została podzielona na trzy strony [form1.xhtml, form2.xhtml, form3.xhtml], na których rozłożono wprowadzane dane. Strona [form4.xhtml] jest kopią strony [index.xhtml] z poprzedniego projektu. W pliku [2] klasa [Form.java] pozostaje niezmieniona. Będzie ona służyć jako szablon dla czterech poprzednich stron JSF,
  2. w pliku [3] dodano stronę [exception.xhtml]: będzie ona wykorzystywana w przypadku wystąpienia wyjątku w aplikacji.

2.7.3. Strony [form.xhtml] i ich szablon [Form.java]

2.7.3.1. Kod stron XHTML

Strona JSF [form1.xhtml] wygląda następująco:


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

i odpowiada następującemu wyświetlaniu:

Należy zwrócić uwagę na następujące kwestie:

  • w wierszu 16 tabela, która wcześniej miała trzy kolumny, ma teraz tylko dwie. Kolumna 3, w której wyświetlane były wartości modelu, została usunięta. Będzie je wyświetlać [form4.xhtml],
  • wiersze 40–46: tabela zawierająca sześć linków. Linki w wierszach 44 i 46 mają nawigację statyczną: ich atrybut „action” jest zakodowany na stałe. Pozostałe linki mają nawigację dynamiczną: ich atrybut „action” odwołuje się do metody w beanie formularza odpowiedzialnej za zwracanie klucza nawigacyjnego. Metody, do których odwołuje się plik [Form.java], to:

// wydarzenia
  public String doAction2(){
    return "form2";
  }
  
  public String doAction4(){
    return "form4";
  }
  
  public String doAlea(){
    // liczba losowa z przedziału od 1 do 3
    int i=1+(int)(3*Math.random());
    // zwracamy klucz nawigacyjny
    return "form"+i;
  }
  
  public String throwException() throws java.lang.Exception{
    throw new Exception("Exception test");
}

Na razie pominęliśmy metodę throwException z wiersza 17. Wrócimy do niej później. Metody doAction2 i doAction4 ograniczają się do zwracania klucza nawigacyjnego bez przeprowadzania żadnego przetwarzania. Równie dobrze moglibyśmy więc napisać:


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

Metoda doAlea generuje natomiast losowy klucz nawigacyjny, którego wartość pochodzi z zbioru {"form1", "form2", "form3"}.

Kod stron [form2.xhtml, form3.xhtml, form3.xhtml] jest analogiczny do kodu strony [form1.xhtml].

2.7.3.2. Okres ważności szablonu [Form.java] dla stron [form*.xhtml]

Rozważmy następującą sekwencję działań:

  • w [1] wypełniamy stronę 1 i przechodzimy do strony 3,
  • w [2] wypełnia się stronę 3 i wraca się do strony 1,
  • w pliku [3] strona 1 pojawia się w takiej postaci, w jakiej została wprowadzona. Następnie powracamy do strony 3,
  • w [4] strona 3 pojawia się w takiej postaci, w jakiej została wprowadzona.

Mechanizm ukrytego pola [javax.faces.ViewState] nie wystarcza do wyjaśnienia tego zjawiska.

Podczas przejścia z [1] do [2] ma miejsce kilka etapów:

  • szablon [Form.java] jest aktualizowany na podstawie POST z [form1.jsp]. W szczególności pole inputText otrzymuje wartość „inny tekst”,
  • klucz nawigacyjny „form3” powoduje wyświetlenie [form3.xhtml]. ViewState zawarty w [form3.xhtml] odzwierciedla stan wyłącznie komponentów z [form3.xhtml], a nie tych z [form1.xhtml].

Podczas przejścia z [2] do [3]:

  • model [Form.java] jest aktualizowany na podstawie modelu POST z [form3.xhtml]. Jeśli wymagany jest okres ważności szablonu [Form.java], tworzony jest zupełnie nowy obiekt [Form.java], który następnie jest aktualizowany na podstawie POST z [form3.xhtml]. W takim przypadku pole inputText w szablonie przywraca swoją wartość domyślną:

  private String inputText="texte";

i zachowuje ją: w pliku POST, który powstał z pliku [form3.xhtml], nic nie powoduje aktualizacji pola inputText, które jest częścią szablonu [form1.xhtml], a nie szablonu [form3.xhtml],

  • klucz nawigacyjny „form1” powoduje wyświetlenie [form1.xhtml]. Strona wyświetla swój szablon. W naszym przypadku pole wprowadzania danych login powiązane z szablonem inputText wyświetli texte, a nie wartość „inny tekst” wprowadzoną w polu [1]. Aby pole inputText zachowało wartość wprowadzoną w polu [1], okres ważności szablonu [Form.java] musi być ustawiony na „sesja”, a nie na „żądanie”. W takim przypadku
    • po zakończeniu operacji POST w ramach [form1.xhtml] szablon zostanie umieszczony w sesji klienta. Pole inputText będzie miało wartość „inny tekst”,
    • w momencie wykonania POST z [form3.xhtml] szablon zostanie wyszukany w tej sesji i zaktualizowany przez POST z [form3.xhtml]. Pole inputText nie zostanie zaktualizowane przez ten proces POST, ale zachowa wartość „inny tekst” uzyskaną w wyniku przetworzenia POST z [form1.xhtml] i [1].

Deklaracja komponentu [Form.java] wygląda zatem następująco:


package forms;

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

@ManagedBean
@SessionScoped
public class Form {

Wiersz 8 nadaje beanowi zakres sesji.

2.7.4. Obsługa wyjątków

Wróćmy do ogólnej architektury aplikacji JSF:

Co się dzieje, gdy obsługa zdarzeń lub model przechwytuje wyjątek pochodzący z warstwy biznesowej, na przykład nieprzewidziane rozłączenie z bazą danych?

  • Obsługa zdarzeń [2a] może przechwycić każdy wyjątek pochodzący z warstwy [métier] i przekazać kontrolerowi [Faces Servlet] klucz nawigacyjny do strony błędu specyficznej dla danego wyjątku,
  • W przypadku szablonów rozwiązanie to nie ma zastosowania, ponieważ gdy wywoływany jest szablon [3,4], trwa właśnie renderowanie konkretnej strony XHTML i nie ma już możliwości wyboru tej strony. Jak zmienić stronę, gdy trwa już jej renderowanie? Prostym rozwiązaniem, które jednak nie zawsze się sprawdza, jest zignorowanie wyjątku, który wówczas zostanie przekazany do kontenera serwletów obsługującego aplikację. Kontener ten można skonfigurować tak, aby wyświetlał określoną stronę w przypadku zgłoszenia wyjątku. Rozwiązanie to jest zawsze możliwe do zastosowania i właśnie je teraz omówimy.

2.7.4.1. Konfiguracja aplikacji internetowej pod kątem obsługi wyjątków

Konfigurację aplikacji internetowej pod kątem obsługi wyjątków przeprowadza się w pliku [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>

W wierszach 32–39 znajduje się definicja dwóch stron błędów. Można użyć dowolnej liczby tagów <error-page>. Tag <location> wskazuje stronę, która ma zostać wyświetlona w przypadku błędu. Typ błędu powiązanego ze stroną można zdefiniować na dwa sposoby:

  • za pomocą tagu <exception-type>, który określa typ wyjątku w języku Java. Zatem tag <error-page> w wierszach 36–39 wskazuje, że jeśli kontener serwletów przechwyci wyjątek typu [java.lang.Exception] lub pochodnego (wiersz 37) podczas działania aplikacji, wówczas musi wyświetlić stronę [/faces/exception.xhtml] (wiersz 38). Przyjmując tutaj najbardziej ogólny typ wyjątku [java.lang.Exception], zapewniamy obsługę wszystkich wyjątków,
  • za pomocą tagu <error-code> (wiersz 33), który definiuje kod błędu HTTP. Na przykład, jeśli przeglądarka żąda strony URL lub [http://machine:port/contexte/P], a strona P nie istnieje w kontekście aplikacji, aplikacja ta nie bierze udziału w generowaniu odpowiedzi. To kontener serwletów generuje tę odpowiedź, wysyłając domyślną stronę błędu. Pierwszy wiersz strumienia HTTP w tej odpowiedzi zawiera kod błędu 404, wskazujący, że żądana strona P nie istnieje. Można chcieć wygenerować odpowiedź, która na przykład będzie zgodna z identyfikacją wizualną aplikacji lub zawierała linki pomagające rozwiązać problem. W takim przypadku należy użyć tagu <error-page> wraz z tagiem <error-code>404</error-code>.

Powyżej kod błędu 500 o wartości HTTP jest kodem zwracanym w przypadku „zawieszenia się” aplikacji. Jest to kod, który zostałby zwrócony, gdyby wyjątek dotarł do kontenera serwletów. Oba tagi <error-page> w wierszach 28–35 są zatem prawdopodobnie zbędne. Umieszczono je oba, aby zilustrować dwa sposoby obsługi błędu.

2.7.4.2. Symulacja wyjątku

Wyjątek jest generowany sztucznie przez link [Lancer une exception]:

Kliknięcie linku [Lancer une exception] [1] powoduje wyświetlenie strony [2].

W kodzie stron [formx.xhtml] link [Lancer une exception] jest generowany w następujący sposób:


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

W wierszu 5 widać, że po kliknięciu linku zostanie wykonana metoda [form].throwException. Wygląda ona następująco:


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

W tej metodzie generowany jest wyjątek typu [java.lang.Exception]. Zostanie on przekazany do kontenera serwletów, który następnie wyświetli stronę [/faces/exception.xhtml].

2.7.4.3. Informacje związane z wyjątkiem

Gdy wyjątek dotrze do kontenera serwletów, kontener ten wyświetli odpowiednią stronę błędu, przekazując jej informacje o wyjątku. Informacje te są umieszczane jako nowe atrybuty przetwarzanego żądania. Żądanie przeglądarki oraz odpowiedź, którą otrzyma, są zawarte w obiektach Java typu [HttpServletRequest request] i [HttpServletResponse response]. Obiekty te są dostępne na wszystkich etapach przetwarzania żądania przeglądarki.

Po otrzymaniu żądania HTTP z przeglądarki kontener serwletów umieszcza je w obiekcie Java typu [HttpServletRequest request] i tworzy obiekt [HttpServletResponse response], który umożliwi wygenerowanie odpowiedzi. W obiekcie tym znajduje się między innymi kanał TCP-IP, który ma być wykorzystany dla strumienia odpowiedzi HTTP. Wszystkie warstwy t1, t2, ..., tn, które będą uczestniczyć w przetwarzaniu obiektu request, mają dostęp do tych dwóch obiektów. Każda z nich może uzyskać dostęp do elementów pierwotnego żądania request i przygotować odpowiedź, uzupełniając obiekt response. Warstwa localisation może na przykład ustalić localisation odpowiedzi za pomocą metody response.setLocale(Locale l).

Poszczególne warstwy ti mogą przekazywać sobie informacje za pośrednictwem obiektu request. Obiekt ten posiada słownik atrybutów, pusty w momencie utworzenia, który może być uzupełniany przez kolejne warstwy przetwarzania. Warstwy te mogą umieścić w atrybutach obiektu request informacje niezbędne dla kolejnej warstwy przetwarzania. Istnieją dwie metody zarządzania atrybutami obiektu request:

  • void setAttribute(String s, Object o), która pozwala dodać do atrybutów obiekt o identyfikowany przez ciąg znaków s,
  • Object getAttribute(String s), która pozwala pobrać atrybut o identyfikowany przez ciąg znaków s.

Gdy wyjątek zostanie zgłoszony do kontenera serwletów, ten umieszcza w przetwarzanym żądaniu następujące atrybuty:

klucz
wartość
javax.servlet.error.status_code
kod błędu HTTP, który zostanie przesłany do klienta
javax.servlet.error.exception
typ wyjątku w Javie wraz z komunikatem o błędzie.
javax.servlet.error.request_uri
URL wywołana w momencie wystąpienia wyjątku
javax.servlet.error.servlet_name
serwlet, który przetwarzał żądanie w momencie wystąpienia wyjątku

Wykorzystamy te atrybuty żądania na stronie [exception.xhtml], aby je wyświetlić.

2.7.4.4. Strona błędu [exception.xhtml]

Jej zawartość jest następująca:


<?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>
        <!-- linki -->
        <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. Wyrażenia na stronie wyjątku

W łańcuchu przetwarzania żądania klienta strona XHTML jest zazwyczaj ostatnim ogniwem łańcucha:

Wszystkie elementy łańcucha są klasami Java, w tym strona XHTML. Jest ona bowiem przekształcana przez kontener serwletów w serwlet c.a.d, a następnie w zwykłą klasę Java. Dokładniej rzecz biorąc, strona XHTML jest przekształcana w kod Java, który jest wykonywany w ramach następującej metody:


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

Począwszy od linii 14 znajduje się kod Java odpowiadający stronie XHTML. Kod ten będzie dysponował pewną liczbą obiektów zainicjowanych przez metodę _jspService, linia 1 powyżej:

  • wiersz 1: HttpServletRequest request: żądanie w trakcie przetwarzania,
  • wiersz 1: HttpServletResponse response: odpowiedź, która zostanie wysłana do klienta,
  • wiersz 7: ServletContext application: obiekt reprezentujący samą aplikację internetową. Podobnie jak obiekt request, obiekt application może posiadać atrybuty. Są one wspólne dla wszystkich żądań wszystkich klientów. Zazwyczaj są to atrybuty tylko do odczytu,
  • wiersz 6: HTTPSession sesja: reprezentuje sesję klienta. Podobnie jak obiekty request i application, obiekt session może posiadać atrybuty. Są one wspólne dla wszystkich żądań tego samego klienta,
  • wiersz 9: JspWriter out: strumień zapisu do przeglądarki klienta. Obiekt ten jest przydatny do debugowania strony XHTML. Wszystko, co zostanie zapisane za pośrednictwem out.println(tekst), zostanie wyświetlone w przeglądarce klienta.

Gdy na stronie JSF zapisuje się #{wyrażenie}, wyrażenie to może być kluczem atrybutu obiektów request, session lub application wymienionych powyżej. Odpowiedni atrybut jest wyszukiwany kolejno w tych trzech obiektach. Zatem #{klucz} jest obliczany w następujący sposób:

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

Gdy tylko zostanie znaleziona wartość inna niż null, obliczanie #{klucz} zostaje przerwane. Aby uzyskać większą precyzję, można wskazać kontekst, w którym należy szukać atrybutu:

  • #{requestScope['clé']}, aby wyszukać atrybut w obiekcie request,
  • #{sessionScope['clé']} – aby wyszukać atrybut w obiekcie session,
  • #{applicationScope['clé']} – aby wyszukać atrybut w obiekcie application.

Tak właśnie zrobiono na stronie [exception.xhtml], strona 116. Wykorzystano następujące atrybuty:

klucz
domena
wartość
javax.servlet.error.status_code
żądanie
patrz punkt 2.7.4.3.
javax.servlet.error.exception
to samo
to samo
javax.servlet.error.request_uri
to samo
to samo
javax.servlet.error.servlet_name
to samo
to samo

Różne komunikaty niezbędne dla stron JSF i [exception.xhtml] zostały dodane do już istniejących plików komunikatów:

[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. Przykład mv-jsf2-06: walidacja i konwersja danych wprowadzonych przez użytkownika

2.8.1. Aplikacja

Aplikacja zawiera formularz do wprowadzania danych. Po jego zatwierdzeniu zwracany jest ten sam formularz wraz z ewentualnymi komunikatami o błędach, jeśli dane zostały uznane za nieprawidłowe.

2.8.2. Projekt NetBeans

Projekt NetBeans dla tej aplikacji wygląda następująco:

Projekt [mv-jsf2-06] ponownie opiera się na jednej stronie [index.html] [1] oraz jej szablonie [Form.java] [2]. Nadal wykorzystuje komunikaty pochodzące z [messages.properties], ale wyłącznie w języku francuskim ([3]). Opcja zmiany języka nie jest dostępna.

2.8.3. Środowisko aplikacji

Poniżej przedstawiamy treść plików konfiguracyjnych aplikacji bez szczegółowych objaśnień. Pliki te pozwalają lepiej zrozumieć poniższe informacje.

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

Wiersz 17 jest nowy. Zostanie on wyjaśniony w dalszej części.

Plik komunikatów [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="!"

Arkusz stylów [styles.css] wygląda następująco:


.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. Strona [index.xhtml] i jej szablon [Form.java]

Strona [index.xhtml] wygląda następująco:


<?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">
        <!-- wiersz 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"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 6 -->
        ...
        <!-- wiersz 7 -->
        ...
        <!-- wiersz 8 -->
        ...
        <!-- wiersz 9 -->
        ...
        <!-- wiersz 10 -->
        ...
        <!-- wiersz 11 -->
        ...
        <!-- wiersz 12 -->
        ...
        <!-- wiersz 13 -->
        ...
      </h:panelGrid>
      <!-- przyciski sterujące -->
      <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>

Główną nowością jest obecność tagów:

  • do wyświetlania komunikatów o błędach <h:messages> (wiersz 14), <h:message> (wiersze 24, 29, 34),
  • które nakładają ograniczenia poprawności na wprowadzane dane <f:validateLongRange> (wiersz 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
  • które definiują konwerter między wpisem a jego szablonem jako <f:convertDateTime>.

Szablonem tej strony jest następująca klasa [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() {
}
// wpisy
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 = "";

// działania
public String submit() {
...
}

public String cancel() {
...
}

// elementy walidacyjne
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// metody pobierające i ustawiające
...
}

Nowością jest tutaj to, że pola szablonu nie są już wyłącznie typu String, ale mają różne typy.

2.8.5. Różne wpisy w formularzu

Przeanalizujemy teraz kolejno poszczególne pola formularza.

2.8.5.1. Pola 1–4: wprowadzanie liczby całkowitej

Strona [index.xhtml] przedstawia pole 1 w następującej postaci:


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

Szablon form.saisie1 jest zdefiniowany w następujący sposób w [Form.java]:


private Integer saisie1 = 0;

W przeglądarce, na stronie GET, strona [index.xhtml] powiązana z jej szablonem [Form.java] wyświetla się następująco:

  • wiersz 2 generuje kod [1],
  • wiersz 3 generuje [2],
  • wiersz 4 generuje [3],
  • wiersz 5 generuje [4].

Załóżmy, że wprowadzono i zatwierdzono następujący wpis:

W formularzu zwróconym przez aplikację otrzymujemy wówczas następujący wynik:

  • w [1] – błędne wprowadzenie danych,
  • w [2] – komunikat o błędzie,
  • w [3] widać, że wartość pola Integer „saisie1” w modelu nie uległa zmianie.

Wyjaśnijmy, co się stało. W tym celu wróćmy do cyklu przetwarzania strony JSF:

Analizujemy ten cykl dla komponentu:


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

oraz jego szablon:


private Integer saisie1 = 0;
  • w [A] przywracana jest strona [index.xhtml] wysłana podczas GET z przeglądarki. W [A] strona wygląda tak, jak ją otrzymał użytkownik. Komponent id="saisie1" przywraca swoją początkową wartość „0”,
  • w [B] elementy strony otrzymują jako wartości dane przesłane przez przeglądarkę. W [B] strona wygląda tak, jak została wprowadzona i zatwierdzona przez użytkownika. Komponent id="saisie1" otrzymuje jako wartość wartość przesłaną „x”,
  • w przypadku [C], jeśli strona zawiera jawne walidatory i konwertery, są one wykonywane. Konwertery domyślne są również uruchamiane, jeśli typ pola powiązanego z komponentem nie jest typu String. Tak jest w tym przypadku, gdzie pole form.saisie1 jest typu Integer. JSF spróbuje przekształcić wartość „x” komponentu id="saisie1" na typ Integer. Spowoduje to błąd, który zatrzyma cykl przetwarzania [A-F]. Błąd ten zostanie przypisany do komponentu id="saisie1". Za pośrednictwem komponentu [D2] następuje następnie bezpośrednie przejście do fazy renderowania odpowiedzi. Zwracana jest ta sama strona [index.xhtml],
  • faza [D] ma miejsce tylko wtedy, gdy wszystkie komponenty strony przeszły fazę konwersji/walidacji. To właśnie w tej fazie wartość komponentu id="saisie1" zostanie przypisana do jego szablonu form.saisie1.

Jeśli faza [C] zakończy się niepowodzeniem, strona zostanie ponownie wyświetlona, a poniższy kod zostanie wykonany ponownie:


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

Komunikat wyświetlany w [2] pochodzi z linii 4 kodu [index.xhtml]. Tag <h:message for="idComposant"/> wyświetla komunikat o błędzie powiązany z komponentem wskazanym przez atrybut for, o ile wystąpi błąd. Komunikat wyświetlany w pliku [2] jest standardowy i znajduje się w pliku [javax/faces/Messages.properties] w archiwum [jsf-api.jar]:

W pliku [2] widać, że plik komunikatów występuje w kilku wariantach. Przyjrzyjmy się zawartości pliku [Messages_fr.properties]:

...
# ==============================================================================
# Błędy komponentów
# ==============================================================================
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.

# ==============================================================================
# Błędy konwertera
# ==============================================================================
...
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}
...


# ==============================================================================
# Błędy walidatora
# ==============================================================================
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.
...

Plik zawiera komunikaty podzielone na kategorie:

  • błędy dotyczące komponentu, wiersz 3,
  • błędy konwersji między komponentem a jego szablonem, wiersz 12
  • błędy walidacji, gdy na stronie występują walidatory, wiersz 23.

Błąd, który wystąpił w komponencie id="saisie1", jest błędem konwersji typu String na typ Integer. Powiązany komunikat o błędzie znajduje się w wierszu 18 pliku komunikatów.

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

Wyświetlony komunikat o błędzie został przytoczony poniżej:

W komunikacie widać, że:

  • parametr {2} został zastąpiony identyfikatorem komponentu, dla którego wystąpił błąd konwersji,
  • parametr {0} został zastąpiony wartością wprowadzoną w [1] dla tego komponentu,
  • parametr {1} został zastąpiony liczbą 9346.

Większość komunikatów związanych z komponentami ma dwie wersje: wersję podsumowującą (summary) i wersję szczegółową (detail). Tak jest w przypadku wierszy 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}

Komunikat o kluczu _detail (wiersz 2) to tzw. komunikat szczegółowy. Drugi to tzw. komunikat podsumowujący. Tag <h:message> domyślnie wyświetla komunikat szczegółowy. Zachowanie to można zmienić za pomocą atrybutów showSummary i showDetail. Tak właśnie zrobiono w przypadku komponentu o identyfikatorze saisie2:


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

W wierszu 2 komponent saisie2 jest powiązany z następującym polem form.saisie2:


  private Integer saisie2 = 0;

Otrzymany wynik jest następujący:

  • w [1] znajduje się komunikat szczegółowy, a w [2] – komunikat podsumowujący.

Tag <h:messages> wyświetla w formie listy wszystkie podsumowane komunikaty o błędach ze wszystkich komponentów, a także komunikaty o błędach niezwiązane z żadnym komponentem. Również w tym przypadku atrybuty mogą zmienić to domyślne zachowanie:

  • showDetail: true / false – określa, czy mają być wyświetlane komunikaty szczegółowe,
  • showSummary: true / false – określa, czy mają być wyświetlane komunikaty podsumowujące,
  • globalOnly: true / false – określa, czy mają być wyświetlane wyłącznie komunikaty o błędach niezwiązane z komponentami. Taki komunikat mógłby na przykład zostać utworzony przez programistę.

Komunikat o błędzie związany z konwersją można zmienić na różne sposoby. Po pierwsze, można poinstruować aplikację, aby korzystała z innego pliku komunikatów. Zmianę tę wprowadza się w [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>

Wiersze 3–8 definiują plik komunikatów, ale nie jest to ten plik, który jest używany przez tagi <h:message> i <h:messages>. Aby go zdefiniować, należy użyć tagu <message-bundle> z wiersza 9. Wiersz 9 informuje tagi <h:message(s)>, że plik [messages.properties] powinien zostać przeszukany przed plikiem [javax.faces.Messages.properties]. Jeśli więc dodamy następujące wiersze do pliku [messages_fr.properties]:


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

błąd zwracany dla komponentów saisie1 i saisie2 będzie brzmiał:

Image

Innym sposobem zmiany komunikatu o błędzie konwersji jest użycie atrybutu converterMessage komponentu, jak pokazano poniżej dla komponentu saisie3:


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

Komponent saisie3 jest powiązany z następującym polem form.saisie3:


  private Integer saisie3 = 0;
  • w wierszu 3 atrybut converterMessage wyraźnie określa komunikat, który ma być wyświetlany w przypadku błędu konwersji,
  • w wierszu 3 atrybut required="true" wskazuje, że wpis jest obowiązkowy. Pole nie może pozostać puste. Pole uznaje się za puste, jeśli nie zawiera żadnych znaków lub zawiera ciąg spacji. Również w tym przypadku w [javax.faces.Messages.properties] istnieje domyślny komunikat:
javax.faces.component.UIInput.REQUIRED={0} : erreur de validation. Vous devez indiquer une valeur.

Atrybut requiredMessage pozwala zastąpić ten domyślny komunikat. Jeśli plik [messages.properties] zawiera następujące komunikaty:


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

można uzyskać następujący wynik:

lub też taki:

Sprawdzenie, czy wprowadzona wartość jest liczbą całkowitą, nie zawsze wystarcza. Czasami trzeba sprawdzić, czy wprowadzona liczba mieści się w określonym przedziale. W takim przypadku stosuje się walidator. Przykładem jest pole nr 4. Jego kod w [index.xhtml] wygląda następująco:


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

W wierszu 3 komponent saisie4 jest powiązany z następującym szablonem form.saisie4:


  private Integer saisie4 = 0;

W wierszach 3–5 tag <h:inputText> posiada tag potomny <f:validateLongRange>, który dopuszcza dwa opcjonalne atrybuty: minimum i maximum. Ten tag, zwany również walidatorem, pozwala na dodanie ograniczenia do wartości wprowadzanej: musi to być nie tylko liczba całkowita, ale liczba całkowita z przedziału [minimum, maximum], jeśli obecne są oba atrybuty minimum i maximum, większa lub równa minimum, jeśli obecny jest tylko atrybut minimum, oraz mniejsza lub równa maximum, jeśli obecny jest tylko atrybut maximum. Walidator <f:validateLongRange> posiada domyślne komunikaty o błędach w [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.

Również w tym przypadku możliwe jest zastąpienie tych komunikatów innymi. Istnieje atrybut validatorMessage, który pozwala zdefiniować konkretny komunikat dla danego komponentu. Tak więc przy użyciu następującego kodu 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>

oraz następującym komunikatem w [messages.properties]:


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

otrzymujemy następujący wynik:

Image

2.8.5.2. Wpisy 5 i 6: wprowadzanie liczby rzeczywistej

Wprowadzanie liczb rzeczywistych podlega zasadom podobnym do tych dotyczących wprowadzania liczb całkowitych. Kod XHTML dla pól 5 i 6 jest następujący:


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

Elementy szablonu [Form.java] powiązane z komponentami saisie5 i saisie6:


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

Komunikaty o błędach związane z konwerterami i modułami walidacyjnymi komponentów saisie5 i saisie6, w [messages.properties]:


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

Oto przykładowe wyniki działania:

Image

2.8.5.3. Wpis 7: wprowadzenie wartości logicznej

Wprowadzanie wartości logicznej powinno zazwyczaj odbywać się za pomocą pola wyboru. Jeśli odbywa się to za pomocą pola tekstowego, ciąg znaków „true” jest konwertowany na wartość logiczną true, a każdy inny ciąg znaków na wartość logiczną false.

Kod XHTML z przykładu:


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

Szablon komponentu saisie7:


  private Boolean saisie7 = true;

Oto przykładowe wprowadzenie danych i odpowiedź:

W [1] znajduje się wprowadzona wartość. W wyniku konwersji ten ciąg znaków „x” staje się wartością logiczną false. Pokazuje to [2]. Wartość [3] w szablonie nie uległa zmianie. Zmienia się ona dopiero wtedy, gdy wszystkie konwersje i walidacje na stronie zakończą się powodzeniem. W tym przykładzie tak się nie stało.

2.8.5.4. Wpis 8: wprowadzenie daty

Wprowadzenie daty odbywa się w tym przykładzie za pomocą następującego kodu XHTML:


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

Komponent saisie8 w wierszu 3 wykorzystuje konwerter java.lang.String <--> java.util.Date. Szablon form.saisie8 powiązany z komponentem saisie8 jest następujący:


  private Date saisie8 = new Date();

Komponent zdefiniowany w wierszach 7–9 również wykorzystuje konwerter, ale wyłącznie w kierunku java.util.Date --> java.lang.String.

Konwerter <f:convertDateTime> obsługuje różne atrybuty, w tym atrybut pattern, który określa format ciągu znaków, który ma zostać przekształcony w datę lub w jaki sposób data ma zostać wyświetlona.

Podczas pierwszego wywołania strony [index.xhtml] poprzedni wiersz 8 wyświetla się następująco:

Pola [1] i [2] wyświetlają wartość szablonu form.saisie8:


  private Date saisie8 = new Date();

gdzie saisie8 przyjmuje wartość bieżącej daty. Konwerter używany w obu przypadkach do wyświetlania daty jest następujący:


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

gdzie dd (day) oznacza numer dnia, MM (Month) – numer miesiąca, a yyyy (year) – rok. W przypadku [1] konwerter służy do konwersji odwrotnej: java.lang.String --> java.util.Date. Aby data była prawidłowa, musi być wprowadzona zgodnie ze wzorem „dd/MM/yyyy”.

W [javax.faces.Messages.properties] dostępne są domyślne komunikaty dotyczące nieprawidłowych dat:

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} 

które można zastąpić własnymi komunikatami. Na przykład:


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

komunikat wyświetlany w przypadku błędu konwersji będzie następującym komunikatem o kluczu saisie8.error:


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

Oto przykład:

Image

2.8.5.5. Wpis 9: wprowadzenie ciągu znaków o określonej długości

Przykład 9 pokazuje, jak wymusić, aby wprowadzony ciąg znaków miał liczbę znaków mieszczącą się w określonym przedziale:


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

W wierszu 4 walidator <f:validateLength minimum="4" maximum="4"/> wymusza, aby wprowadzony ciąg znaków miał dokładnie 4 znaki. Można użyć tylko jednego z atrybutów: „minimum” dla minimalnej liczby znaków lub „maximum” dla maksymalnej liczby znaków.

Szablon form.saisie9 dla komponentu saisie9 z wiersza 3 wygląda następująco:


  private String saisie9 = "";

Dla tego typu walidacji istnieją domyślne komunikaty o błędach:

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

które można zastąpić, używając atrybutu validatorMessage, tak jak w wierszu 3 powyżej. Komunikat o kluczu saisie9.error brzmi następująco:


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

Oto przykład wykonania:

Image

2.8.5.6. Wprowadzenie 9B: wprowadzenie ciągu znaków zgodnego z szablonem

Wprowadzenie 9B pokazuje, jak wymusić, aby wprowadzony ciąg znaków miał liczbę znaków mieszczącą się w określonym przedziale:


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

W wierszu 4 walidator <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> wymusza, aby wprowadzony ciąg znaków odpowiadał wzorcowi wyrażenia regularnego, w tym przypadku: ciąg 0 lub więcej spacji, 2 cyfry, znak :, 2 cyfry, sekwencja 0 lub więcej spacji.

Wzorzec form.saisie9B dla komponentu saisie9B w wierszu 3 wygląda następująco:


private String saisie9B;

Dla tego typu walidacji istnieją domyślne komunikaty o błędach:

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

które można zastąpić, używając atrybutu validatorMessage, tak jak w wierszu 3 powyżej. Komunikat o kluczu saisie9.error brzmi następująco:


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

Oto przykład wykonania:

Image

2.8.5.7. Wpis 10: napisanie specyficznej metody walidacji

Podsumowując: JSF pozwala na sprawdzenie, spośród wprowadzonych wartości, poprawności liczb (liczb całkowitych, rzeczywistych), dat, długości ciągów znaków oraz zgodności wprowadzonych danych z wyrażeniem regularnym. JSF pozwala na dodanie własnych walidatorów i konwerterów do istniejących walidatorów i konwerterów. Kwestia ta nie jest tutaj omawiana, ale można zapoznać się z [ref2], aby zgłębić ten temat.

Przedstawiamy tutaj inną metodę: polegającą na walidacji wprowadzonych danych za pomocą metody modelu formularza. Oto przykład:


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

Model form.saisie10 powiązany z komponentem saisie10 z wiersza 3 wygląda następująco:


  private Integer saisie10 = 0;

Chcemy, aby wprowadzona liczba była mniejsza niż 1 lub większa niż 7. Nie można tego sprawdzić za pomocą podstawowych walidatorów komponentu JSF. Należy zatem napisać własną metodę walidacji dla komponentu saisie10. Wskazuje się ją za pomocą atrybutu validator komponentu, który ma być walidowany:


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

Komponent saisie10 jest walidowany przez metodę form.validateSaisie10. Wygląda ona następująco:


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

Sygnatura metody walidacyjnej musi być zgodna z tą z wiersza 1:

  • FacesContext context: kontekst wykonania strony – zapewnia dostęp do różnych informacji, w szczególności do obiektów HttpServletRequest request i HttpServletResponse response,
  • UIComponent component: komponent, który należy zweryfikować. Tag <h:inputText> jest reprezentowany przez komponent typu UIInput wywodzący się z UIComponent. W tym przypadku to właśnie ten komponent UIInput jest przekazywany jako drugi parametr,
  • Wartość obiektu: wprowadzona wartość, która ma zostać sprawdzona, przekształcona na typ z modelu. Należy pamiętać, że jeśli konwersja typu String na typ z modelu nie powiodła się, metoda walidacji nie zostanie wykonana. Wejście do metody validateSaisie10 oznacza, że konwersja typu String na Integer zakończyła się powodzeniem. Trzeci parametr ma wówczas typ Integer.
  • wiersz 2: wprowadzona wartość jest przekształcana na typ int,
  • wiersz 3: sprawdzane jest, czy wprowadzona wartość jest mniejsza od 1 lub większa od 7. Jeśli tak, walidacja zostaje zakończona. Jeśli nie, moduł walidujący musi zgłosić błąd, wywołując wyjątek typu ValidatorException.

Klasa ValidatorException posiada dwa konstruktory:

  • konstruktor [1] przyjmuje jako parametr komunikat o błędzie typu FacesMessage. Ten typ komunikatu jest wyświetlany przez tagi <h:messages> i <h:message>,
  • konstruktor [2] pozwala ponadto na enkapsulację przyczyny typu Throwable lub pochodnej tego błędu.

Musimy utworzyć komunikat typu FacesMessage. Klasa ta posiada różne konstruktory:

Konstruktor [1] definiuje właściwości obiektu FacesMessage:

  • FacesMessage.Severity severity: poziom ważności wybrany z poniższej wyliczanki: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
  • String summary: skrócona wersja komunikatu o błędzie – wyświetlana jest za pomocą tagów <h:message showSummary="true"> oraz <h:messages>,
  • String detail: szczegółowa wersja komunikatu o błędzie – wyświetlana jest za pomocą tagów <h:message> oraz <h:messages showDetail="true">.

Można użyć dowolnego z konstruktorów, a brakujące parametry można ustalić później za pomocą metod set.

Konstruktor [1] nie pozwala na wskazanie komunikatu znajdującego się w pliku komunikatów zinternacjonalizowanych. To oczywiście szkoda. David Geary i Cay Horstmann w swojej książce „Core JavaServer Faces” wypełniają tę lukę za pomocą klasy narzędziowej com.corejsf.util.Messages. To właśnie ta klasa jest wykorzystywana w linii 4 kodu Java do utworzenia komunikatu o błędzie. Zawiera ona wyłącznie metody statyczne, w tym metodę getMessage używaną w linii 4:


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

Metoda getMessage przyjmuje trzy parametry:

  • String bundleName: nazwa pliku komunikatów bez rozszerzenia .properties, ale z nazwą pakietu. W tym przypadku naszym pierwszym parametrem mógłby być messages, aby wskazać plik [messages.properties]. Przed użyciem pliku wskazanego przez pierwszy parametr, getMessage próbuje wykorzystać plik komunikatów aplikacji, o ile taki istnieje. Jeśli więc w pliku [faces-config.xml] zadeklarowano plik komunikatów za pomocą znacznika:

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

można przekazać null jako pierwszy parametr do metody getMessage. Tak właśnie zrobiono w tym przypadku (patrz [web.xm], strona 120),

  • Ciąg znaków resourceId: klucz komunikatu, który ma zostać przetworzony w pliku komunikatów. Widzieliśmy już, że komunikat może mieć zarówno wersję skróconą, jak i szczegółową. resourceId to identyfikator wersji skróconej. Wersja szczegółowa zostanie automatycznie wyszukana na podstawie klucza resourceId_detail. W ten sposób w [messages.properties] znajdą się dwa komunikaty dotyczące błędu w wpisie nr 10:

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

Komunikat typu FacesMessage generowany przez metodę Messages.getMessage zawiera zarówno wersję skróconą, jak i szczegółową, o ile zostały one znalezione. Obie wersje muszą być obecne, w przeciwnym razie zgłaszany jest wyjątek typu [NullPointerException],

  • Object[] params: rzeczywiste parametry komunikatu, jeśli posiada on parametry formalne {0}, {1}, ... Te parametry formalne zostaną zastąpione elementami tablicy params.

Wróćmy do kodu metody walidacji komponentu 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);
    }
}
  • w [4] komunikat typu FacesMessage jest tworzony za pomocą metody statycznej Messages.getMessage,
  • w [5] ustala się poziom ważności komunikatu,
  • w [6] wywoływany jest wyjątek typu ValidatorException z wcześniej utworzonym komunikatem. Metoda walidacji została wywołana przez następujący kod XHTML:

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

W wierszu 3 metoda walidacji jest wykonywana dla komponentu o identyfikatorze saisie10. W związku z tym komunikat o błędzie wygenerowany przez metodę validateSaisie10 jest powiązany z tym komponentem i w związku z tym wyświetlany w wierszu 4 (atrybut for="saisie10"). Domyślnie za pomocą tagu <h:message> wyświetlana jest wersja szczegółowa.

Oto przykładowe wykonanie:

Image

2.8.5.8. Wpisy 11 i 12: walidacja grupy komponentów

Do tej pory omówione metody walidacji sprawdzały poprawność tylko jednego komponentu. Jak postąpić, jeśli wymagana walidacja dotyczy kilku komponentów? Temu właśnie przyjrzymy się teraz. W formularzu:

Image

chcemy, aby pola 11 i 12 zawierały dwie liczby całkowite, których suma wynosi 10.

Kod JSF będzie wyglądał następująco:


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

a powiązany szablon:


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

W wierszu 3 kodu JSF wykorzystujemy już przedstawione techniki, aby sprawdzić, czy wartość wprowadzona dla komponentu saisie11 jest rzeczywiście liczbą całkowitą. To samo dotyczy wiersza 11 dla komponentu saisie12. Aby sprawdzić, czy saisie11 + saisie12 = 10, można by stworzyć specjalny moduł walidacyjny. Jest to rozwiązanie preferowane. Ponownie odczytamy [ref2], aby je odkryć. Tutaj stosujemy inne podejście.

Strona [index.xhtml] jest zatwierdzana za pomocą przycisku [Valider], którego kod JSF wygląda następująco:


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

gdzie komunikat msg['submit'] ma następującą treść:


submit=Valider

W wierszu 3 widać, że metoda form.submit zostanie wykonana w celu przetworzenia kliknięcia przycisku [Valider]. Wygląda ona następująco:


  // działania
  public String submit() {
    // ostatnie zatwierdzenia
    validateForm();
    // przesyłamy ten sam formularz
    return null;
  }

  // ogólne zatwierdzenia
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
...
}

Ważne jest, aby zrozumieć, że w momencie wykonania metody submit:

  • wszystkie walidatory i konwertery formularza zostały już uruchomione i zakończyły się powodzeniem,
  • pola modelu [Form.java] otrzymały wartości przesłane przez klienta.

Wróćmy bowiem do cyklu przetwarzania POST JSF:

Metoda submit jest procedurą obsługi zdarzeń. Obsługuje ona zdarzenie clic związane z przyciskiem [Valider]. Podobnie jak wszystkie procedury obsługi zdarzeń, jest ona wykonywana w fazie [E], po tym jak wszystkie walidatory i konwertery zostały uruchomione i zakończyły się powodzeniem ([C]), a szablon został zaktualizowany o przesłane wartości ([D]). Nie ma więc już mowy o generowaniu wyjątków typu [ValidatorException], jak to robiliśmy wcześniej. Zadowolimy się odesłaniem formularza wraz z komunikatami o błędach:

W [1] powiadomimy użytkownika, a w [2] i [3] umieścimy oznaczenie błędu. W kodzie JSF komunikat [1] zostanie uzyskany w następujący sposób:


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

W wierszu 2 tag <h:messages> domyślnie wyświetla skróconą wersję komunikatów o błędach dotyczących wszystkich błędnych wpisów w komponentach formularza, a także wszystkie komunikaty o błędach niezwiązane z komponentami. Atrybut globalOnly="true" ogranicza wyświetlanie do tych ostatnich.

Komunikaty [2] i [3] są wyświetlane za pomocą prostych tagów <h:outputText>:


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

W wierszach 4–7 komponent saisie11 może generować dwa komunikaty o błędach:

  • ten wskazujący na błędną konwersję lub brak danych. Komunikat ten, wygenerowany przez sam program JSF, będzie zawarty w typie FacesMessage i wyświetlony za pomocą tagu <h:message> w wierszu 5,
  • który wygenerujemy, jeśli wpis11 + wpis12 nie jest równe 10. Zostanie on wyświetlony w wierszu 6. Komunikat o błędzie będzie zawarty w szablonie form.errorSaisie11.

Oba komunikaty dotyczą błędów, które nie mogą wystąpić jednocześnie. Sprawdzanie, czy wprowadzona_11 + wprowadzona_12 = 10, odbywa się w metodzie submit, która jest wykonywana tylko wtedy, gdy w formularzu nie ma już żadnych błędów. Gdy metoda ta zostanie wykonana, komponent saisie11 zostanie sprawdzony, a jego szablon form.saise11 otrzyma swoją wartość. Komunikat z wiersza 5 nie będzie już mógł zostać wyświetlony. Z drugiej strony, jeśli komunikat z wiersza 5 zostanie wyświetlony, oznacza to, że w formularzu pozostał co najmniej jeden błąd, a metoda submit nie zostanie wykonana. Komunikat z wiersza 6 nie zostanie wyświetlony. Aby oba możliwe komunikaty o błędach znalazły się w tej samej kolumnie tabeli, zostały one zebrane w tagu <h:panelGroup> (wiersze 4 i 7).

Metoda submit wygląda następująco:


  // działania
  public String submit() {
    // ostatnie weryfikacje
    validateForm();
    // przesyłamy ten sam formularz
    return null;
  }

  // walidacje globalne
  private void validateForm() {
    if ((saisie11 + saisie12) != 10) {
      // komunikat ogólny
      FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
      message.setSeverity(FacesMessage.SEVERITY_ERROR);
      FacesContext context = FacesContext.getCurrentInstance();
      context.addMessage(null, message);
      // komunikaty związane z polami
      message = Messages.getMessage(null, "error.sign", null);
      setErrorSaisie11(message.getSummary());
      setErrorSaisie12(message.getSummary());
    } else {
      setErrorSaisie11("");
      setErrorSaisie12("");
    }
}
  • wiersz 4: metoda submit wywołuje metodę validateForm w celu przeprowadzenia ostatnich weryfikacji,
  • wiersz 11: sprawdzane jest, czy saisie11+saisie12 = 10,
  • jeśli nie, w wierszach 13–14 tworzy się komunikat typu FacesMessage z komunikatem o identyfikatorze saisies11et12.incorrectes. Wygląda on następująco:

saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
  • tak utworzony komunikat jest dodawany (wiersze 15–16) do listy komunikatów o błędach aplikacji. Komunikat ten nie jest powiązany z żadnym konkretnym komponentem. Jest to komunikat ogólny aplikacji. Zostanie on wyświetlony za pomocą znacznika <h:messages globalOnly="true"/> przedstawionego powyżej,
  • wiersz 18: tworzymy nowy komunikat typu FacesMessage z komunikatem o identyfikatorze error.sign. Brzmi on następująco:

error.sign="!"

Wspomnieliśmy, że metoda statyczna [Messages.getMessage] tworzy komunikat typu FacesMessage wraz z wersją skróconą i szczegółową, o ile takie istnieją. W tym przypadku istnieje jedynie wersja skrócona komunikatu error.sign. Wersję skróconą komunikatu m uzyskuje się za pomocą funkcji m.getSummary(). W wierszach 19 i 20 skrócona wersja komunikatu error.sign jest umieszczana w polach errorSaisie11 i errorSaisie12 szablonu. Zostaną one wyświetlone za pomocą następujących znaczników JSF:


          <h:outputText value="#{form.saisie11}"/>
          ...
          <h:outputText value="#{form.saisie12}"/>
  • wiersze 22–23: jeśli właściwość saisie11+saisie12=10 jest sprawdzona, wówczas oba pola errorSaisie11 i errorSaisie12 w szablonie są czyszczone, aby usunąć ewentualny poprzedni komunikat o błędzie. Należy pamiętać, że szablon jest zachowywany między żądaniami w sesji klienta.

Oto przykładowe wykonanie:

W kolumnie [1] widać, że szablon otrzymał przesłane wartości, co oznacza, że wszystkie operacje walidacji i konwersji między przesłanymi wartościami a szablonem zakończyły się powodzeniem. W ten sposób mógł zostać uruchomiony menedżer zdarzeń form.submit, który obsługuje kliknięcie przycisku [Valider]. To właśnie on wygenerował komunikaty wyświetlone w [2] i [3]. Widać, że model został zaktualizowany, mimo że formularz został odrzucony i odesłany do klienta. W takiej sytuacji można by oczekiwać, że szablon nie powinien być aktualizowany. Rzeczywiście, zakładając, że użytkownik anuluje aktualizację za pomocą przycisku [Annuler] [4], nie będzie można powrócić do pierwotnego szablonu, chyba że został on wcześniej zapisany.

2.8.5.9. POST – formularz bez sprawdzania danych

Rozważmy powyższy formularz i załóżmy, że użytkownik, nie rozumiejąc swoich błędów, chce przerwać wypełnianie formularza. Wykorzysta wówczas przycisk [Annuler] wygenerowany przez następujący kod JSF:


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

W wierszu 4 komunikat msg['cancel'] brzmi następująco:


cancel=Annuler

Metoda form.cancel powiązana z przyciskiem [Annuler] zostanie wykonana tylko wtedy, gdy formularz jest poprawny. Pokazaliśmy to już w przypadku metody form.submit powiązanej z przyciskiem [Valider]. Jeśli użytkownik chce anulować wypełnianie formularza, sprawdzanie poprawności wprowadzonych danych jest oczywiście zbędne. Wynik ten uzyskuje się dzięki atrybutowi immediate="true", który nakazuje metodzie JSF wykonanie metody form.cancel bez przechodzenia przez fazę walidacji i konwersji. Wróćmy do cyklu przetwarzania POST JSF:

Zdarzenia komponentów akcji <h:commandButton> oraz <h:commandLink> posiadające atrybut immediate="true" są przetwarzane w fazie [C], a następnie cykl JSF przechodzi bezpośrednio do fazy [E], w której następuje renderowanie odpowiedzi.

Metoda form.cancel wygląda następująco:


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

Jeśli w poprzednim formularzu użyje się przycisku [Annuler], otrzymuje się w odpowiedzi następującą stronę:

  • ponownie otrzymujemy formularz, ponieważ menedżer zdarzeń form.cancel zwraca klucz nawigacyjny null. W związku z tym zwracana jest strona [index.xhtml],
  • szablon [Form.java] został zmodyfikowany przez metodę form.cancel. Znajduje to odzwierciedlenie w kolumnie [2], która wyświetla ten szablon,
  • natomiast kolumna [3] odzwierciedla wartość zaksięgowaną dla komponentów.

Wróćmy do kodu JSF komponentu saisie1 [4];


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

W wierszu 4 wartość komponentu saisie1 jest powiązana z szablonem form.saisie1. Powoduje to kilka rzeczy:

  • podczas operacji GET na [index.xhtml] komponent saisie1 wyświetli wartość szablonu form.saisie1,
  • gdy POST jest pochodną [index.xhtml], wartość przesłana dla komponentu saisie1 jest przypisywana do szablonu form.saisie1 tylko wtedy, gdy wszystkie walidacje i konwersje formularza zakończą się powodzeniem. Niezależnie od tego, czy model został zaktualizowany przez przesłane wartości, czy nie, jeśli formularz zostanie odesłany po zakończeniu operacji POST, komponenty wyświetlają wartość, która została przesłana, a nie wartość z powiązanego z nimi modelu. Widać to na powyższym zrzucie ekranu, gdzie kolumny [2] i [3] nie mają tych samych wartości.

2.9. Przykład mv-jsf2-07: zdarzenia związane ze zmianą stanu komponentów JSF

2.9.1. Aplikacja

Aplikacja przedstawia przykład POST zrealizowany bez użycia przycisku lub linku. Formularz wygląda następująco:

Zawartość listy combo2 [2] jest powiązana z elementem wybranym w combo1 [1]. Po zmianie zaznaczenia w [1] wykonywany jest element POST formularza, w trakcie którego zawartość elementu combo2 jest modyfikowana w celu odzwierciedlenia elementu wybranego w [1], a następnie formularz jest zwracany. W trakcie tego POST nie przeprowadza się żadnej walidacji.

2.9.2. Projekt NetBeans

Projekt NetBeans aplikacji wygląda następująco:

Mamy jeden formularz [index.xhtml] wraz z jego szablonem [Form.java].

2.9.3. Środowisko aplikacji

Plik komunikatów [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

Arkusz stylów [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. Formularz [index.xhtml]

Formularz [index.xhtml] wygląda następująco:


<?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">
        <!-- nagłówki -->
        <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"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 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}"/>
        <!-- wiersz 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>
      <!-- przyciski sterujące -->
      <h:panelGrid columns="2" border="0">
        <h:commandButton value="#{msg['submit']}"/>
        ...
      </h:panelGrid>
    </h:form>
  </h:body>
</html>

Nowością jest kod listy combo1, wiersze 24–26. Pojawiają się nowe atrybuty:

  • onchange: atrybut HTML – deklaruje funkcję lub kod JavaScript, który ma zostać wykonany w momencie zmiany elementu zaznaczonego w combo1. W tym przypadku kod JavaScript submit() wysyła formularz na serwer,
  • valueChangeListener: atrybut JSF – określa nazwę metody, która ma zostać wykonana po stronie serwera w przypadku zmiany elementu wybranego w combo1. Łącznie wykonywane są dwie metody: jedna po stronie klienta, druga po stronie serwera,
  • immediate=true: atrybut JSF – określa moment, w którym ma zostać uruchomiony handler zdarzenia po stronie serwera: po odtworzeniu formularza zgodnie z danymi wprowadzonymi przez użytkownika, ale przed sprawdzeniem poprawności wprowadzonych danych. Chodzi tutaj o wypełnienie listy combo2 na podstawie elementu wybranego z listy combo1, nawet jeśli w pozostałej części formularza mogą znajdować się błędne wpisy. Oto przykład:
  • w [1] – pierwszy wpis,
  • w [2] przenosimy wybrany element z combo1 z A do B.

Otrzymany wynik jest następujący:

Nastąpiło POST. Zawartość combo2 i [2] została dostosowana dowybrany element w combo1 i [1], mimo że wpis w [3] był nieprawidłowy. To właśnie atrybut immediate=true spowodował, że metoda form.combo1ChangeListener została wykonana przed kontrolami poprawności. Gdyby nie ten atrybut, metoda ta nie zostałaby wykonana, ponieważ cykl przetwarzania zatrzymałby się na sprawdzaniu poprawności z powodu błędu w [3].

Komunikaty związane z formularzem w [messages.properties] są następujące:


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

Okres ważności [Form.java] jest ustawiony na „request”:


package forms;

...

@ManagedBean
@RequestScoped
public class Form {

W wierszu 6 zakres działania bean jest ustawiony na „request”.

2.9.5. Szablon [Form.java]

Szablon [Form.java] wygląda następująco:


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() {
  }
  
// pola formularza
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // pola robocze
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // metody
  public SelectItem[] getCombo1Items(){
    // inicjowanie combo1
    SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
    for(int i=0;i<combo1Labels.length;i++){
      combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
    }
    return combo1Items;
  }
  
  public SelectItem[] getCombo2Items(){
    // inicjowanie combo2 w zależności od 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;
  }
  
  // słuchacze
  public void combo1ChangeListener(ValueChangeEvent event){
    // śledzenie
    logger.info("combo1ChangeListener");
    // pobieramy wartość przesłaną z combo1
    combo1Label=(String)event.getNewValue();
    // zwracamy odpowiedź, ponieważ chcemy ominąć walidacje
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // kontynuacja
    logger.info("raz");
    // wyzerowanie formularza
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
  
// metody pobierające i ustawiające
  ...
}

Powiążmy formularz [index.xhtml] z jego szablonem [Form.java]:

Lista combo1 jest generowana przez następujący kod JSF:


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

Pobiera ona swoje elementy za pomocą metody getCombo1Items swojego szablonu (wiersz 2). Metoda ta jest zdefiniowana w wierszach 28–35 kodu Java. Generuje ona listę trzech elementów {"A", "B", "C"}.

Lista combo2 jest generowana przez następujący kod JSF:


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

Pobiera ona swoje elementy za pomocą metody getCombo2Items swojego modelu (wiersz 2). Metoda ta jest zdefiniowana w wierszach 37–44 kodu Java. Generuje ona listę składającą się z pięciu elementów {"X1", "X2", "X3”, „X4”, „X5”}, gdzie X oznacza element combo1Label z wiersza 16. Zatem podczas początkowego generowania formularza lista combo2 zawiera elementy {"A1","A2","A3”, „A4”, „A5”.

Gdy użytkownik zmieni element wybrany na liście combo1,

  • zdarzenie onchange="submit();" zostanie przetworzone przez przeglądarkę klienta. Formularz zostanie zatem wysłany na serwer,
  • po stronie serwera JSF wykryje, że komponent combo1 zmienił wartość. Zostanie wykonana metoda combo1ChangeListener z wierszy 47–54. Metoda typu ValueChangeListener otrzymuje jako parametr obiekt typu javax.faces.event.ValueChangeEvent. Obiekt ten pozwala uzyskać poprzednią i nową wartość komponentu, którego wartość uległa zmianie, za pomocą następujących metod:

Image

W tym przypadku komponentem jest lista combo1 typu UISelectOne. Jej wartość jest typu String.

  • wiersz 51 szablonu Java: nowa wartość combo1 jest zapisywana w combo1Label, który służy do generowania elementów listy combo2,
  • wiersz 53: zwracamy odpowiedź. Należy tu pamiętać, że menedżer combo1ChangeListener jest uruchamiany z atrybutem immediate="true". Jest więc uruchamiany po fazie, w której drzewo komponentów strony zostało zaktualizowane o przesłane wartości, a przed procesem walidacji tych wartości. Chcemy jednak uniknąć tego procesu walidacji, ponieważ lista combo2 musi zostać zaktualizowana, nawet jeśli w formularzu pozostały błędne wpisy. Żądamy zatem, aby odpowiedź została wysłana natychmiast, bez przechodzenia przez fazę walidacji wpisów.
  • Formularz zostanie odesłany w takiej postaci, w jakiej został wypełniony. Jednak elementy list combo1 i combo2 nie są wartościami przesłanymi. Zostaną one wygenerowane ponownie poprzez wywołanie metod getCombo1Items i getCombo2Items. Ta ostatnia metoda wykorzysta wówczas nową wartość combo1Label ustaloną przez combo1ChangeListener, a elementy listy combo2 ulegną zmianie.

2.9.6. Przycisk [Raz]

Za pomocą przycisku [Raz] chcemy przywrócić formularz do stanu początkowego, jak pokazano poniżej:

W przypadku przycisku [1] formularz przed naciśnięciem przycisku POST odpowiada stanowi z przycisku [Raz], a w przypadku przycisku [2] – stanowi z przycisku POST.

Chociaż pod względem funkcjonalnym jest to proste, obsługa tego przypadku użycia okazuje się dość złożona. Można wypróbować różne rozwiązania, w szczególności to zastosowane dla przycisku [Annuler] z poprzedniego przykładu:


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

gdzie metoda form.raz wygląda następująco:


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

Wynik uzyskany za pomocą przycisku [Raz] z poprzedniego przykładu wygląda zatem następująco:

Kolumna [1] pokazuje, że metoda form.raz została wykonana. Jednak kolumna [1] nadal wyświetla wartości zaksięgowane:

  • dla combo1 zaksięgowaną wartością było „B”. Element ten jest zatem zaznaczony na liście,
  • dla combo2 zaksięgowaną wartością było „B5”. W wyniku wykonania form.raz elementy {„B1”, ..., „B5”} z combo2 zostały zmienione na {„A1”, ..., „A5”}. Element „B5” już nie istnieje i dlatego nie można go wybrać. Wyświetlany jest wówczas pierwszy element listy,
  • w przypadku saisie1 wartość przesłana wynosiła 10.

Jest to normalne zachowanie przy atrybucie immediate=&quot;true&quot;. Aby uzyskać inny wynik, należy przesłać wartości, które chcemy zobaczyć w nowym formularzu, nawet jeśli użytkownik wprowadził inne wartości. Osiąga się to za pomocą niewielkiego kodu JavaScript po stronie klienta. Formularz wygląda wówczas następująco:


<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}"/>
  • w wierszu 10 atrybut onclick='raz()' nakazuje wykonanie funkcji JavaScript raz, gdy użytkownik kliknie przycisk [Raz],
  • wiersz 3: elementowi o nazwie „formulaire:combo1” przypisuje się wartość „A”. Poszczególne elementy wiersza 3 są następujące:
    • dokument: strona wyświetlana przez przeglądarkę,
    • document.forms: zbiór formularzy w dokumencie,
    • document.forms['formulaire']: formularz z atrybutem name="formulaire",
    • documents.forms['formulaire'].elements: zbiór elementów formularza posiadających atrybut name="formulaire",
    • document.forms['formulaire'].elements['formulaire:combo1']: element formularza posiadający atrybut name="formulaire:combo1"
    • document.forms['formulaire'].elements['formulaire:combo1'].value: wartość, która zostanie przesłana przez element formularza posiadający atrybut name="formulaire:combo1".

Aby poznać atrybuty name poszczególnych elementów strony wyświetlanej przez przeglądarkę, można sprawdzić jej kod źródłowy (poniżej z IE7):

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

Biorąc to pod uwagę, można zrozumieć, że w kodzie JavaScript funkcji raz:

  • wiersz 3 powoduje, że wartość przesłana dla komponentu combo1 będzie ciągiem znaków A,
  • wiersz 4 powoduje, że wartość przekazana do komponentu combo2 będzie ciągiem znaków A1,
  • wiersz 5 powoduje, że wartość przesłana dla komponentu saisie1 będzie ciągiem znaków 0.

W ten sposób zostanie wywołany element POST formularza, powiązany z każdym przyciskiem typu <h:commandButton> (wiersz 10). Zostanie wykonana metoda form.raz, a formularz zostanie zwrócony w postaci, w jakiej został przesłany. Otrzymujemy wówczas następujący wynik:

Wynik ten kryje w sobie wiele informacji. Wartości „A”, „A1” i „0” komponentów combo1, combo2 oraz saisie1 są wysyłane na serwer. Załóżmy, że poprzednia wartość komponentu combo1 wynosiła „B”. Oznacza to zmianę wartości komponentu combo1, w związku z czym powinna zostać również wykonana metoda form.combo1ChangeListener. Mamy dwa procedury obsługi zdarzeń o atrybucie immediate="true". Czy zostaną one wykonane obie? Jeśli tak, to w jakiej kolejności? A może tylko jedna? Jeśli tak, to która?

Aby uzyskać więcej informacji, tworzymy logi w aplikacji:


package forms;

import java.util.logging.Logger;
...
public class Form {
  
...  
// pola formularza
  private String combo1="A";
  private String combo2="A1";
  private Integer saisie1=0;
  
  // pola robocze
  final private String[] combo1Labels={"A","B","C"};
  private String combo1Label="A";
  private static final Logger logger=Logger.getLogger("forms.Form");
  
  // słuchacz
  public void combo1ChangeListener(ValueChangeEvent event){
    // śledzenie
    logger.info("combo1ChangeListener");
    // pobieramy wartość przesłaną z combo1
    combo1Label=(String)event.getNewValue();
    // zwracamy odpowiedź, ponieważ chcemy ominąć walidacje
    FacesContext.getCurrentInstance().renderResponse();
  }
  
  public String raz(){
    // kontynuacja
    logger.info("raz");
    // wyzerowanie formularza
    combo1Label="A";
    combo1="A";
    combo2="A1";
    saisie1=0;
    return null;
  }
...
}
  • wiersz 16: tworzony jest generator logów. Parametr getLogger pozwala rozróżnić źródła logów. W tym przypadku generator logów nosi nazwę forms.Form,
  • wiersz 21: rejestrujemy przejście do metody combo1ChangeListener,
  • wiersz 30: rejestruje się wejście do metody raz.

Jakie wpisy w dzienniku generuje przycisk [Raz] lub zmiana wartości combo1? Rozważmy różne przypadki:

  • używamy przycisku [Raz], podczas gdy element wybrany w combo1 to „A”. „A” jest zatem ostatnią wartością komponentu combo1. Widzieliśmy, że przycisk [Raz] uruchamiał funkcję JavaScript, która wysyłała wartość „A” dla komponentu combo1. Komponent combo1 nie zmienia zatem swojej wartości. Z logów wynika zatem, że wykonywana jest wyłącznie metoda form.raz:
  
  • używa się przycisku [Raz], podczas gdy element wybrany w combo1 nie ma wartości „A”. Komponent combo1 zmienia zatem swoją wartość: jego ostatnia wartość nie była „A”, a przycisk [Raz] nadaje mu wartość „A”. Logi pokazują wówczas, że wykonywane są dwie metody. W kolejności: combo1ChangeListener, raz:
  
  • zmieniamy wartość combo1 bez użycia przycisku [Raz]. Logi pokazują, że wykonywana jest tylko metoda combo1ChangeListener:
  

2.10. Przykład mv-jsf2-08: tag <h:dataTable>

2.10.1. Aplikacja

Aplikacja wyświetla listę osób z możliwością ich usuwania:

  • w [1] – lista osób,
  • w [2] – linki umożliwiające ich usunięcie.

2.10.2. Projekt NetBeans

Projekt NetBeans tej aplikacji wygląda następująco:

Mamy jeden formularz [index.xhtml] wraz z jego szablonem [Form.java].

2.10.3. Środowisko aplikacji

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

Plik komunikatów [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

Arkusz stylów [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. Formularz [index.xhtml] i jego szablon [Form.java]

Przypomnijmy widok powiązany ze stroną [index.xhtml]:

  

Formularz [index.xhtml] wygląda następująco:


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

W wierszu 14 tag <h:dataTable> wykorzystuje pole #{form.personnes} jako źródło danych. Wygląda to następująco:

private List<Osoba> osoby;

Klasa [Personne] wygląda następująco:


package forms;

public class Personne {
  // dane
  private int id;
  private String nom;
  private String prénom;
  
  // producenci
  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);
  }
  
  // metody pobierające i ustawiające
...
}

Wróćmy do treści tagu <h:dataTable>:


<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
  • atrybut var="personne" określa nazwę zmiennej reprezentującej bieżącą osobę wewnątrz tagu <h:datatable>,
  • atrybut headerClass="headers" określa styl nagłówków kolumn tabeli,
  • atrybut columnClasses="...." określa styl każdej z kolumn tabeli.

Przyjrzyjmy się jednej z kolumn tabeli i zobaczmy, jak jest zbudowana:

  

Kod XHTML kolumny Id wygląda następująco:


<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>
  • wiersze 3–7: kolumna „id” tabeli,
  • wiersze 8–13: kolumna „nazwisko” w tabeli,
  • wiersze 14–19: kolumna „prénom” tabeli.

Przyjrzyjmy się teraz kolumnie linków [Retirer]:

Ta kolumna jest generowana przez następujący kod:


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

Link [Retirer] jest generowany przez wiersze 4–6. Po kliknięciu linku zostanie wykonana metoda [Form].retirerPersonne. Czas przyjrzeć się klasie [Form.java]:


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;

  // konstruktor
  public Form() {
    // inicjalizacja listy osób
    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() {
    // wyszukiwanie wybranej osoby
    int i = 0;
    for (Personne personne : personnes) {
      // czy aktualna osoba = wybrana osoba?
      if (personne.getId() == personneId) {
        // usuwanie aktualnej osoby z listy
        personnes.remove(i);
        // zakończono
        break;
      } else {
        // następna osoba
        i++;
      }
    }
    // testujemy na tej samej stronie
    return null;
  }
  
  // metody pobierające i ustawiające
...
}
  • wiersze 18–24: konstruktor inicjuje listę osób z wiersza 14,
  • wiersz 10: ponieważ lista ta musi istnieć przez cały czas trwania żądań, zakresem działania fasoli jest sesja.

Gdy wykonywana jest metoda [retirerPersonne] z wiersza 26, pole z wiersza 15 zostało zainicjowane identyfikatorem osoby, której link został kliknięty ([Retirer]):


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

Tag <f:setPropertyActionListener> umożliwia przekazanie informacji do modelu. W tym przypadku wartość atrybutu „value” jest kopiowana do pola modelu określonego przez atrybut „target”. W ten sposób identyfikator bieżącej osoby, którą należy usunąć z listy osób, jest kopiowany do pola [Form].personneId za pośrednictwem metody pobierającej tego pola. Odbywa się to przed wykonaniem metody, do której odwołuje się atrybut action w wierszu 1.

W wierszach 26–43 metoda [supprimerPersonne] usuwa osobę, której id jest równe personneId.

2.11. Przykład mv-jsf2-09: układ strony aplikacji JSF

2.11.1. Aplikacja

Aplikacja pokazuje, jak zaprojektować układ aplikacji JSF z dwoma widokami:

Aplikacja posiada dwa widoki:

  • w [1] – strona 1,
  • w [2] – strona 2.

Można przechodzić między tymi dwiema stronami. Chcemy tutaj pokazać, że strony 1 i 2 mają wspólny układ, jak widać na powyższych zrzutach ekranu.

2.11.2. Projekt NetBeans

Projekt aplikacji w NetBeans wygląda następująco:

Aplikacja zawiera wyłącznie strony o nazwach XHTML. Nie ma do nich przypisanych szablonów Java.

2.11.3. Strona [layout.xhtml]

Strona [layout.xhtml] określa format stron aplikacji:


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

W wierszu 7 pojawia się nowa przestrzeń nazw ui. Ta przestrzeń nazw zawiera tagi umożliwiające formatowanie stron aplikacji. Tagi z tej przestrzeni są używane w wierszach 17, 22, 25 i 32.

Strona [layout.xhtml] wyświetla informacje w tabeli HTML (wiersz 14). Stronę tę można wyświetlić w przeglądarce:

  • w [1], wywołano URL.

Pole [2] zostało wygenerowane przez następujący kod 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>

Tag <ui:include> w wierszu 6 pozwala na włączenie do strony zewnętrznego kodu XHTML. Plik [entete.xhtml] ma następującą treść:


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

Cały kod z wierszy 3–8 zostanie wstawiony do pliku [layout.xhtml]. W ten sposób tagi <html> i <body> zostaną wstawione do tagu <td>. Nie powoduje to żadnych błędów. Zatem strony wstawiane za pomocą <ui:include> są kompletnymi stronami XHTML. Z wizualnego punktu widzenia wpływ będzie miała jedynie linia 6. Tagi <html> i <body> występują ze względów syntaktycznych.

Obszar [3] został wygenerowany przez następujący kod 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>

Tag <ui:include> w wierszu 5 zawiera następujący plik [menu.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">
  <body>
    <h2>menu</h2>
  </body>
</html>

Obszar [4] został wygenerowany przez następujący kod 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>

Tag <ui:include> w wierszu 6 zawiera następujący plik [basdepage.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">
  <body>
    <h2>bas de page</h2>
  </body>
</html>

Obszar [5] został wygenerowany przez następujący kod XHTML:


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

Tag <ui:insert> w wierszu 5 definiuje obszar o nazwie „treść”. Jest to obszar, który może zawierać zmienną treść. Zobaczymy, jak to działa. Kiedy wywołaliśmy stronę [layout.xhtml], nie zdefiniowano żadnej treści dla obszaru o nazwie „treść”. W takim przypadku wykorzystywana jest treść tagu <ui:insert> z wierszy 4–6. Wyświetlany jest zatem wiersz 5.

2.11.4. Strona [page1.xhtml]

Strona [layout.xhtml] nie jest przeznaczona do wyświetlania. Służy ona jako szablon dla stron [page1.xhtml] i [page2.xhtml]. Mówimy tu o szablonie stron. Strona [page1.xhtml] wygląda następująco:


<?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>
  • w wierszu 6 użyto przestrzeni nazw ui,
  • w wierszu 7 za pomocą tagu <ui:composition> wskazuje się, że strona jest powiązana z szablonem [layout.xhtml],
  • w wierszu 8 to powiązanie powoduje, że każdy tag <ui:define> zostanie powiązany z tagiem <ui:insert> w używanym szablonie, w tym przypadku [layout.xhtml]. Powiązanie odbywa się poprzez atrybut name obu tagów. Muszą one być identyczne.

Wyświetlana strona to [layout.xhtml], gdzie zawartość każdego tagu <ui:insert> jest zastępowana zawartością tagu <ui:define> z żądanej strony. W tym przypadku wszystko przebiega tak, jakby wyświetlana strona miała postać:


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

Wiersze 25–26 strony [page1.xhtml] zostały wstawione w miejsce tagu <ui:insert> ze strony [layout.xml].

Strona [page2.xhtml] jest analogiczna do strony [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

Przeprowadzona właśnie analiza pliku JSF 2 nie jest bynajmniej wyczerpująca. Jest jednak wystarczająca do zrozumienia poniższych przykładów. Aby pogłębić wiedzę, warto zapoznać się z plikiem [ref2].

2.13. Testy w środowisku Eclipse

Pokażmy, jak przeprowadzać testy projektów Maven za pomocą pakietu narzędzi SpringSource:

  • w [1] importujemy projekt Maven [2], który wybieramy za pomocą przycisku [3]. W tym przypadku wybieramy projekt Maven [mv-jsf2-09] dla Eclipse
  • w [4], zaimportowany projekt został prawidłowo rozpoznany jako projekt Maven [5],
  • do [6], a projekt zaimportowany do eksploratora projektów,
  • w [7] uruchamia się go na serwerze [8] Tomcat [9],
  • w [10] uruchomiono Tomcat 7,
  • w [11] strona główna projektu [mv-jsf2-09] [11] wyświetla się w przeglądarce wewnętrznej środowiska Eclipse.