précédentsommairesuivant

III. Java Server Faces

Nous présentons maintenant le framework Java Server Faces. Ce sera la version 2 qui sera utilisée mais les exemples présentent principalement des caractéristiques la version 1. Nous présenterons de la version 2, les seules caractéristiques nécessaires à l'application exemple qui suivra.

III-A. La place de JSF dans une application web

Tout d'abord, situons JSF dans le développement d'une application web. Le plus souvent, celle-ci sera bâtie sur une architecture multicouche telle que la suivante :

Image non disponible
  • la couche [web] est la couche en contact avec l'utilisateur de l'application web. Celui-ci interagit avec l'application web au travers de pages web visualisées par un navigateur. C'est dans cette couche que se situe JSF et uniquement dans cette couche,
  • la couche [métier] implémente les règles de gestion de l'application, tels que le calcul d'un salaire ou d'une facture. Cette couche utilise des données provenant de l'utilisateur via la couche [web] et du Sgbd via la couche [DAO],
  • la couche [DAO] (Data Access Objects), la couche [jpa] (Java Persistence Api) et le pilote JDBC gèrent l'accès aux données du Sgbd. Le couche [jpa] sert d'ORM (Object Relational Mapper). Elle fait un pont entre les objets manipulés par la couche [DAO] et les lignes et les colonnes des données d'une base de données relationnelle,
  • l'intégration des couches peut être réalisée par un conteneur Spring ou EJB3 (Enterprise Java Bean).

Les exemples donnés dans la suite pour illustrer JSF, n'utiliseront qu'une seule couche, la couche [web] :

Image non disponible

Une fois les bases de JSF acquises, nous construirons des applications Java EE multicouche.

III-B. Le modèle de développement MVC de JSF

JSF implémente le modèle d'architecture dit MVC (Modèle - Vue - Contrôleur) de la façon suivante :

Image non disponible

Cette architecture implémente le Design Pattern MVC (Modèle, Vue, Contrôleur). Le traitement d'une demande d'un client se déroule selon les quatre étapes suivantes :

  1. demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des clients. C'est la porte d'entrée de l'application. C'est le C de MVC,
  2. traitement - le contrôleur C traite cette demande. Pour ce faire, il se fait aider par des gestionnaires d'événements spécifiques à l'application écrite [2a]. Ces gestionnaires peuvent avoir besoin de l'aide de la couche métier [2b]. Une fois la demande du client traitée, celle-ci peut appeler diverses réponses. Un exemple classique est :
  3. navigation - le contrôleur choisit la réponse (= vue) à envoyer au client. Choisir la réponse à envoyer au client nécessite plusieurs étapes :
  • une page d'erreurs si la demande n'a pu être traitée correctement ;
  • une page de confirmation sinon,
  • choisir la Facelet qui va générer la réponse. C'est ce qu'on appelle la vue V, le V de MVC. Ce choix dépend en général du résultat de l'exécution de l'action demandée par l'utilisateur ;
  • fournir à cette Facelet les données dont elle a besoin pour générer cette réponse. En effet, celle-ci contient le plus souvent des informations calculées par le contrôleur. Ces informations forment ce qu'on appelle le modèle M de la vue, le M de MVC,

L'étape 3 consiste donc en le choix d'une vue V et en la construction du modèle M nécessaire à celle-ci.

  1. réponse - le contrôleur C demande à la Facelet choisie de s'afficher. Celle-ci utilise le modèle M préparé par le contrôleur C pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. La forme exacte de celle-ci peut être diverse : ce peut être un flux HTML, PDF, Excel…

Dans un projet JSF :

  • le contrôleur C est la servlet [javax.faces.webapp.FacesServlet]. On trouve celle-ci dans la bibliothèque [javaee.jar],
  • les vues V sont implémentées par des pages utilisant la technologie des Facelets,
  • les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing beans" ou plus simplement beans.

Maintenant, précisons le lien entre architecture web MVC et architecture en couches. Ce sont deux concepts différents qui sont parfois confondus. Prenons une application web JSF à une couche :

Image non disponible

Si nous implémentons la couche [web] avec JSF, nous aurons bien une architecture web MVC mais pas une architecture multicouche. Ici, la couche [web] s'occupera de tout : présentation, métier, accès aux données. Avec JSF, ce sont les beans qui feront ce travail.

Maintenant, considérons une architecture web multicouche :

Image non disponible

La couche [web] peut être implémentée sans framework et sans suivre le modèle MVC. On a bien alors une architecture multicouche mais la couche web n'implémente pas le modèle MVC.

Dans MVC, nous avons dit que le modèle M était celui de la vue V, c.a.d. l'ensemble des données affichées par la vue V. Une autre définition du modèle M de MVC est souvent donnée :

Image non disponible

Beaucoup d'auteurs considèrent que ce qui est à droite de la couche [web] forme le modèle M du MVC. Pour éviter les ambigüités on parlera :

  • du modèle du domaine lorsqu'on désigne tout ce qui est à droite de la couche [web],
  • du modèle de la vue lorsqu'on désigne les données affichées par une vue V.

Dans la suite, le terme " modèle M " désignera exclusivement le modèle d'une vue V.

III-C. Exemple mv-jsf2-01 : les éléments d'un projet JSF

Les premiers exemples seront réduits à la seule couche web implémentée avec JSF 2 :

Image non disponible

Lorsque les bases seront acquises, nous étudierons des exemples plus complexes avec des architectures multicouche.

III-C-1. Génération du projet

Nous générons notre premier projet JSF2 avec Netbeans 7.

Image non disponible
  • en [1], créer un nouveau projet,
  • en [2], choisir la catégorie [Maven] et le type de projet [Web Application],
  • Image non disponible
  • en [3], désigner le dossier parent du dossier du nouveau projet,
  • en [4], donner un nom au projet,
  • en [5], choisir un serveur. Avec Netbeans 7, on a le choix entre les serveurs Apache Tomcat et Glassfish. La différence entre les deux est que Glassfish supporte les EJB (Enterprise Java Bean) et Tomcat non. Nos exemples JSF ne vont pas utiliser d'EJB. Donc ici, on peut choisir n'importe quel serveur,
  • en [6], on choisit la version Java EE 6 Web,
  • en [7], le projet généré.

Examinons les éléments du projet et explicitons le rôle de chacun.

Image non disponible
  • en [1] : les différentes branches du projet :
  • [Web Pages] : contiendra les pages web (.xhtml, .jsp, .html), les ressources (images, documents divers), la configuration de la couche web ainsi que celle du framework JSF ;
  • [Source packages] : les classes Java du projet ;
  • [Dependencies] : les archives .jar nécessaires au projet et gérées par le framework Maven ;
  • [Java Dependencies] : les archives .jar nécessaires au projet et non gérées par le framework Maven ;
  • [Project Files] : fichier de configuration de Maven et Netbeans,Image non disponible
  • en [2] : la branche [Web Pages],

Elle contient la page [index.jsp] suivante :

[index.jsp]
CacherSélectionnez

C'est une page web qui affiche la chaîne de caractères 'Hello World' en gros caractères.

Le fichier [META-INF/context.xml] est le suivant :

Context.xml
CacherSélectionnez

La ligne 2 indique que le contexte de l'application (ou son nom) est /mv-jsf2-01. Cela signifie que les pages web du projet seront demandées via une URL de la forme http://machine:port/mv-jsf2-01/page. Le contexte est par défaut le nom du projet. Nous n'aurons pas à modifier ce fichier.

Image non disponible
  • en [3], la branche [Source Packages],

Cette branche contient les codes source des classes Java du projet. Ici nous n'avons aucune classe. Netbeans a généré un package par défaut qui peut être supprimé [4].

Image non disponible
  • en [5], la branche [Dependencies],

Cette branche affiche toute les bibliothèques nécessaires au projet et gérées par Maven. Toutes les bibliothèques listées ici vont être automatiquement téléchargées par Maven. C'est pourquoi un projet Maven a besoin d'un accès Internet. Les bibliothèques téléchargées vont être stockées en local. Si un autre projet a besoin d'une bibliothèque déjà présente en local, celle-ci ne sera alors pas téléchargée. Nous verrons que cette liste de bibliothèques ainsi que les dépôts où on peut les trouver sont définis dans le fichier de configuration du projet Maven.

Image non disponible
  • en [6], les bibliothèques nécessaires au projet et non gérées par Maven,
    Image non disponible
  • en [7], les fichiers de configuration du projet Maven :
  • [nb-configuration.xml] est le fichier de configuration de Netbeans. Nous ne nous y intéresserons pas.
  • [pom.xml] : le fichier de configuration de Maven. POM signifie Project Object Model. On sera parfois amené à intervenir directement sur ce fichier.

Le fichier [pom.xml] généré est le suivant :

[pom.xml]
CacherSélectionnez
  • les lignes 5-8 définissent l'objet (artifact) Java qui va être créé par le projet Maven. Ces informations proviennent de l'assistant qui a été utilisé lors de la création du projet :
Image non disponible

Un objet Maven est défini par quatre propriétés :

  • [groupId] : une information qui ressemble à un nom de package. Ainsi les bibliothèques du framework Spring ont groupId=org.springframework, celles du framework JSF ont groupId=javax.faces,
  • [artifactId] : le nom de l'objet Maven. Dans le groupe [org.springframework] on trouve ainsi les artifactId suivants : spring-context, spring-core, spring-beans… Dans le groupe [javax.faces], on trouve l'artifactId jsf-api,
  • [version] : n° de version de l'artifact Maven. Ainsi l'artifact org.springframework.spring-core a les versions suivantes : 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1…
  • [packaging] : la forme prise par l'artifact, le plus souvent war ou jar.

Notre projet Maven va donc générer un [war] (ligne 8) dans le groupe [istia.st] (ligne 5), nommé [mv-jsf2-01] (ligne 6) et de version [1.0-SNAPSHOT] (ligne 7). Ces quatre informations doivent définir de façon unique un artifact Maven.

Les lignes 17-24 listent les dépendances du projet Maven, c'est-à-dire la liste des bibliothèques nécessaires au projet. Chaque bibliothèque est définie par les quatre informations (groupId, artifactId, version, packaging). Lorsque l'information packaging est absente comme ici, le packaging jar est utilisé. On y ajoute une autre information, scope qui fixe à quels moments de la vie du projet on a besoin de la bibliothèque. La valeur par défaut est compile qui indique que la bibliothèque est nécessaire à la compilation et à l'exécution. La valeur provided signifie que la bibliothèque est nécessaire lors de la compilation mais pas lors de l'exécution. Ici à l'exécution, elle sera fournie par le serveur Tomcat 7.

III-C-2. Exécution du projet

Nous exécutons le projet :

Image non disponible

En [1], le projet Maven est exécuté. Le serveur Tomcat est alors lancé s'il ne l'était pas déjà. Un navigateur est lancé également et l'URL du contexte du projet est demandée [2]. Comme aucun document n'est demandé, la page index.html, index.jsp, index.xhtml est alors utilisée si elle existe. Ici, ce sera la page [index.jsp].

III-C-3. Le système de fichiers d'un projet Maven

Image non disponible
  • [1] : le système de fichiers du projet est dans l'onglet [Files],
  • [2] : les sources Java sont dans le dossier [src / main / java],
  • [3] : les pages web sont dans le dossier [src / main / webapp],
  • [4] : le dossier [target] est créé par la construction (build) du projet,
  • [5] : ici, la construction du projet a créé une archive [mv-jsf2-01-1.0-SNAPSHOT.war]. C'est cette archive qui a été exécutée par le serveur Tomcat.

III-C-4. Configurer un projet pour JSF

Notre projet actuel n'est pas un projet JSF. Il lui manque les bibliothèques du framework JSF. Pour faire du projet courant, un projet JSF on procède de la façon suivante :

Image non disponible
  • en [1], on accède aux propriétés du projet,
  • en [2], on choisit la catégorie [Frameworks],
  • en [3], on ajoute un framework,
    Image non disponible
  • en [4], on choisit Java Server Faces,
  • en [5], Netbeans nous propose la version 2.1 du framework. On l'accepte,
  • en [6], le projet s'enrichit alors de nouvelles dépendances.

Le fichier [pom.xml] a évolué pour refléter cette nouvelle configuration :

[pom.xml]
CacherSélectionnez

Lignes 14-33, de nouvelles dépendances ont été ajoutées. Maven les télécharge automatiquement. Il va les chercher dans ce qu'on appelle des dépôts. Le dépôt central (Central Repository) est automatiquement utilisé. On peut ajouter d'autres dépôts grâce à la balise <repository>. Ici deux dépôts ont été ajoutés :

  • lignes 46-51 : un dépôt pour la bibliothèque JSF 2,
  • lignes 52-57 : un dépôt pour la bibliothèque JSTL 1.1.

Le projet s'est également enrichi d'une nouvelle page web :

Image non disponible

La page [index.xhtml] est la suivante :

Index.html
CacherSélectionnez

On a là un fichier XML (ligne 1). On y retrouve les balises du HTML mais au format XML. On appelle cela du XHTML. La technologie utilisée pour créer des pages web avec JSF 2 s'appelle Facelets. Aussi appelle-t-on parfois la page XHTML une page Facelet.

Les lignes 3-4 définissent la balise <html> avec des espaces de noms XML (xmlns=XML Name Space).

  • la ligne 3 définit l'espace de noms principal http://www.w3.org/1999/xhtml,
  • la ligne 4 définit l'espace de noms http://java.sun.com/jsf/html des balises HTML. Celles-ci seront préfixées par h: comme indiqué par xmlns:h. On trouve ces balises aux lignes 5, 7, 8 et 10.

A la rencontre de la déclaration d'un espace de noms, le serveur web va explorer les dossiers [META-INF] du Classpath de l'application, à la recherche de fichiers avec le suffixe .tld (TagLib Definition). Ici, il va les trouver dans l'archive [jsf-impl.jar] [1,2] :

Image non disponible

Examinons [3] le fichier [HTML_basic.tld] :

[HTML_basic.tld]
CacherSélectionnez
  • en ligne 19, l'uri de la bibliothèque de balises,
  • en ligne 16, son nom court.

Les définitions des différentes balises <h:xx> sont trouvées dans ce fichier. Ces balises sont gérées par des classes Java qu'on trouve également dans l'artifact [jsf-impl.jar].

Revenons à notre projet JSF. Il s'est enrichi d'une nouvelle branche :

Image non disponible

La branche [Other Sources] [1] contient les fichiers qui doivent être dans le Classpath du projet et qui ne sont pas du code Java. Ce sera le cas des fichiers de messages en JSF. Nous avons vu que sans l'ajout du framework JSF au projet, cette branche est absente. Pour la créer, il suffit de créer le dossier [src / main / resources] [3] dans l'onglet [Files] [2].

Enfin, un nouveau dossier est apparu dans la branche [Web Pages] :

Image non disponible

Le dossier [WEB-INF] a été créé avec dedans le fichier []. Celui-ci configure l'application web :

[web.xml]
CacherSélectionnez
  • les lignes 7-10 définissent une servlet, c.a.d. une classe Java capable de traiter les demandes des clients. Une application JSF fonctionne de la façon suivante :
Image non disponible

Cette architecture implémente le Design Pattern MVC (Modèle, Vue, Contrôleur). Nous rappelons ce qui a déjà été écrit plus haut. Le traitement d'une demande d'un client se déroule selon les quatre étapes suivantes :

1 - demande - le client navigateur fait une demande au contrôleur [Faces Servlet]. Celui-ci voit passer toutes les demandes des clients. C'est la porte d'entrée de l'application. C'est le C de MVC,

2 - traitement - le contrôleur C traite cette demande. Pour ce faire, il se fait aider par des gestionnaires d'événements spécifiques à l'application écrite [2a]. Ces gestionnaires peuvent avoir besoin de l'aide de la couche métier [2b]. Une fois la demande du client traitée, celle-ci peut appeler diverses réponses. Un exemple classique est :

  • une page d'erreurs si la demande n'a pu être traitée correctement ;
  • une page de confirmation sinon,

3 - navigation - le contrôleur choisit la réponse (= vue) à envoyer au client. Choisir la réponse à envoyer au client nécessite plusieurs étapes :

  • choisir la Facelet qui va générer la réponse. C'est ce qu'on appelle la vue V, le V de MVC. Ce choix dépend en général du résultat de l'exécution de l'action demandée par l'utilisateur ;
  • fournir à cette Facelet les données dont elle a besoin pour générer cette réponse. En effet, celle-ci contient le plus souvent des informations calculées par le contrôleur. Ces informations forment ce qu'on appelle le modèle M de la vue, le M de MVC,

L'étape 3 consiste donc en le choix d'une vue V et en la construction du modèle M nécessaire à celle-ci.

4 - réponse - le contrôleur C demande à la Facelet choisie de s'afficher. Celle-ci utilise le modèle M préparé par le contrôleur C pour initialiser les parties dynamiques de la réponse qu'elle doit envoyer au client. La forme exacte de celle-ci peut être diverse : ce peut être un flux HTML, PDF, Excel…

Dans un projet JSF :

  • le contrôleur C est la servlet [javax.faces.webapp.FacesServlet],
  • les vues V sont implémentées par des pages utilisant la technologie des Facelets,
  • les modèles M et les gestionnaires d'événements sont implémentés par des classes Java souvent appelées "backing beans" ou plus simplement Beans.

Revenons sur le contenu du fichier [web.xml] :

[web.xml]
CacherSélectionnez
  • lignes 12-15 : la balise <servlet-mapping> sert à associer une servlet à une URL demandée par le navigateur client. Ici, il est indiqué que les URL de la forme [/faces/*] doivent être traitées par la servlet de nom [Faces Servlet]. Celle-ci est définie lignes 7-10. Comme il n'y a pas d'autre balise <servlet-mapping> dans le fichier, cela signifie que la servlet [Faces Servlet] ne traitera que les URL de la forme [/faces/*]. Nous avons vu que le contexte de l'application s'appelait [/mv-jsf2-01]. Les URL des clients traitées par la servlet [Faces Servlet] auront donc la forme [http://machine:port/mv-jsf2-01/faces/*]. Les pages .html et .jsp seront traitées par défaut par le conteneur de servlets lui-même, et non par une servlet particulière. En effet, le conteneur de servlets sait comment les gérer,
  • lignes 7-10 : définissent la servlet [Faces Servlet]. Comme toutes les URL acceptées sont dirigées vers elle, elle est le contrôleur C du modèle MVC,
  • ligne 10 : indique que la servlet doit être chargée en mémoire dès le démarrage du serveur web. Par défaut, une servlet n'est chargée qu'à réception de la première demande qui lui est faite,
  • lignes 3-6 : définissent un paramètre destiné à la servlet [Faces Servlet]. Le paramètre javax.faces.PROJECT_STAGE définit l'étape dans laquelle se trouve le projet exécuté. Au stade Development la servlet [Faces Servlet] fait afficher des messages d'erreur utiles au débogage. Au stade Production ces messages ne sont plus affichés,
  • lignes 17-19 : durée en minutes d'une session. Un client dialogue avec l'application par une suite de cycles demande / réponse. Chaque cycle utilise une connexion TCP-IP qui lui est propre, nouvelle à chaque nouveau cycle. Aussi, si un client C fait deux demandes D1 et D2, le serveur S n'a pas les moyens de savoir que les deux demandes appartiennent au même client C. Le serveur S n'a pas la mémoire du client. C'est le protocole HTTP utilisé (HyperText Transport Protocol) qui veut ça : le client dialogue avec le serveur par une succession de cycles demande client / réponse serveur utilisant à chaque fois une nouvelle connexion TCP-IP. On parle de protocole sans état. Dans d'autres protocoles, comme par exemple FTP (File Transfer Protocol), le client C utilise la même connexion pendant la durée de son dialogue avec le serveur S. Une connexion est donc liée à un client particulier. Le serveur S sait toujours à qui il a affaire. Afin de pouvoir reconnaître qu'une demande appartient à un client donné, le serveur web peut utiliser la technique de la session :
  • lors de la première demande d'un client, le serveur S lui envoie la réponse attendue plus un jeton, une suite de caractères aléatoire, unique à ce client ;
  • lors de chaque demande suivante, le client C renvoie au serveur S le jeton qu'il a reçu, permettant ainsi au serveur S de le reconnaître.

L'application a désormais la possibilité de demander au serveur de mémoriser des informations associées à un client donné. On parle de session client. La ligne 18 indique que la durée de vie d'une session est de 30 mn. Cela signifie que si un client C ne fait pas de nouvelle demande pendant 30 mn, sa session est détruite et les informations qu'elle contenait, perdues. Lors de sa prochaine demande, tout se passera comme s'il était un nouveau client et une nouvelle session démarrera,

  • lignes 21-23 : la liste des pages à afficher lorsque l'utilisateur demande le contexte sans préciser de page, par exemple ici [http://machine:port/mv-jsf2-01]. Dans ce cas, le serveur web (pas la servlet) recherche si l'application a défini une balise <welcome-file-list>. Si oui, il affiche la première page trouvée dans la liste. Si elle n'existe pas, la deuxième page, et ainsi de suite jusqu'à trouver une page existante. Ici, lorsque le client demande l'URL [http://machine:port/mv-jsf2-01], c'est l'URL [http://machine:port/mv-jsf2-01/index.xhtml] qui lui sera servie.

III-C-5. Exécuter le projet

Lorsqu'on exécute le nouveau projet, le résultat obtenu dans le navigateur est suivant :

Image non disponible
  • en [1], le contexte a été demandé sans précision de document,
  • en [2], comme il a été expliqué, c'est alors la page d'accueil (welcome-file) [index.xhtml] qui est servie.

On peut avoir la curiosité de regarder le code source reçu [3] :

Code HTML
CacherSélectionnez

On a reçu du HTML. Toutes les balises <h:xx> de index.xhtml ont été traduites dans leurs correspondants HTML.

III-C-6. Le dépôt Maven local

Nous avons dit que Maven téléchargeait les dépendances nécessaires au projet et les stockait localement. On peut explorer ce dépôt local :

Image non disponible
  • en [1], on choisit l'option [Window / Other / Maven Repository Browser],
  • en [2], un onglet [Maven Repositories] s'ouvre,
  • en [3], il contient deux branches, une pour le dépôt local, l'autre pour le dépôt central. Ce dernier est gigantesque. Pour visualiser son contenu, il faut mettre à jour son index [4]. Cette mise à jour dure plusieurs dizaines de minutes.
    Image non disponible
  • en [5], les bibliothèques du dépôt local,
  • en [6], on y trouve une branche [istia.st] qui correspond au [groupId] de notre projet,
  • en [7], on accède aux propriétés du dépôt local,
  • en [8], on a le chemin du dépôt local. Il est utile de le connaître car parfois (rarement) Maven n'utilise plus la dernière version du projet. On fait des modifications et on constate qu'elles ne sont pas prises en compte. On peut alors supprimer manuellement la branche du dépôt local correspondant à notre [groupId]. Cela force Maven à recréer la branche à partir de la dernière version du projet.

III-C-7. Chercher un artifact avec Maven

Apprenons maintenant à chercher un artifact avec Maven. Partons de la liste des dépendances actuelles du fichier [pom.xml] :

[pom.xml]
CacherSélectionnez

Les lignes 13-40 définissent des dépendances et les lignes 45-58 les dépôts où on peut les trouver, outre le dépôt central qui lui, est toujours utilisé. On va modifier les dépendances pour utiliser les bibliothèques dans leur version la plus récente.

Image non disponible

Tout d'abord, nous supprimons les dépendances actuelles [1]. Le fichier [pom.xml] est alors modifié :

[pom.xml]
CacherSélectionnez

Lignes 5-12, les dépendances supprimées n'apparaissent plus dans [pom.xml]. Maintenant, recherchons-les dans les dépôts Maven.

Image non disponible
  • en [1], on ajoute une dépendance au projet,
  • en [2], on doit préciser des informations sur l'artifact cherché (groupId, artifactId, version, packaging (Type) et scope). Nous commençons par préciser le [groupId] [3],
  • en [4], nous tapons [espace] pour faire afficher la liste des artifacts possibles. Ici [jsf-api] et [jsf-impl]. Nous choisissons [jsf-api],
  • en [5], en procédant de la même façon, on choisit la version la plus récente. Le type de packaging est jar.

Nous procédons ainsi pour tous les artifacts :

Image non disponible
Image non disponible

En [6], les dépendances ajoutées apparaissent dans le projet. Le fichier [pom.xml] reflète ces changements :

[pom.xml]
CacherSélectionnez

Supposons maintenant qu'on ne connaisse pas le [groupId] de l'artifact que l'on désire. Par exemple, on veut utiliser Hibernate comme ORM (Object Relational Mapper) et c'est tout ce qu'on sait. On peut aller alors sur le site [http://mvnrepository.com/] :

Image non disponible

En [1], on peut taper des mots clés. Tapons hibernate et lançons la recherche.

Image non disponible
  • en [2], choisissons le [groupId] org.hibernate et l'[artifactId] hibernate-core,
  • en [3], choisissons la version 4.1.2-Final,
  • en [4], nous obtenons le code Maven à coller dans le fichier [pom.xml]. Nous le faisons.
[pom.xml]
CacherSélectionnez

Nous sauvegardons le fichier [pom.xml]. Maven entreprend alors le téléchargement des nouvelles dépendances. Le projet évolue comme suit :

Image non disponible
  • en [5], la dépendance [hibernate-core-4.1.2-Final]. Dans le dépôt où il a été trouvé, cet [artifactId] est lui aussi décrit par un fichier [pom.xml]. Ce fichier a été lu et Maven a découvert que l'[artifactId] avait des dépendances. Il les télécharge également. Il fera cela pour chaque [artifactId] téléchargé. Au final, on trouve en [6] des dépendances qu'on n'avait pas demandées directement. Elles sont signalées par une icône différente de celle de l'[artifactId] principal.

Dans ce document, nous utilisons Maven principalement pour cette caractéristique. Cela nous évite de connaître toutes les dépendances d'une bibliothèque que l'on veut utiliser. On laisse Maven les gérer. Par ailleurs, en partageant un fichier [pom.xml] entre développeurs, on est assuré que chaque développeur utilise bien les mêmes bibliothèques.

Dans les exemples qui suivront, nous nous conterons de donner le fichier [pom.xml] utilisé. Le lecteur n'aura qu'à l'utiliser pour se trouver dans les mêmes conditions que le document. Par ailleurs les projets Maven sont reconnus par les principaux IDE Java (Eclipse, Netbeans, IntelliJ, Jdeveloper). Aussi le lecteur pourra-t-il utiliser son IDE favori pour tester les exemples.

III-D. Exemple mv-jsf2-02 : gestionnaire d'événement - internationalisation - navigation entre pages

III-D-1. L'application

L'application est la suivante :

Image non disponible
  • en [1], la page d'accueil de l'application,
  • en [2], deux liens pour changer la langue des pages de l'application,
  • en [3], un lien de navigation vers une autre page,
  • lorsqu'on clique sur [3], la page [4] est affichée,
  • le lien [5] permet de revenir à la page d'accueil.Image non disponible
  • sur la page d'accueil [1], les liens [2] permettent de changer de langue,
  • en [3], la page d'accueil en anglais.

III-D-2. Le projet Netbeans

On génèrera un nouveau projet web comme expliqué au paragraphe , page . On le nommera mv-jsf2-02 :

Image non disponible
  • en [1], le projet généré,
  • en [2], on a supprimé le package [istia.st.mvjsf202] et le fichier [index.jsp],
  • en [3], on a rajouté des dépendances Maven au moyen du fichier [pom.xml] suivant :
[pom.xml]
CacherSélectionnez

Les dépendances ajoutées sont celles du framework JSF. Il suffit de copier les lignes ci-dessus dans le fichier [pom.xml] à la place des anciennes dépendances.

Image non disponible
  • en [4, 5] : on crée un dossier [src / main / resources] dans l'onglet [Files],
  • en [6], dans l'onglet [Projects], cela a créé la branche [Other Sources].

Nous avons désormais un projet JSF. Nous y créerons différents types de fichiers :

  • des pages web au format XHTML,
  • des classes Java,
  • des fichiers de messages,
  • le fichier de configuration du projet JSF.

Voyons comment créer chaque type de fichier :

Image non disponible
  • en [1], nous créons une page JSF
  • en [2], nous créons une page [index.xhtml] au format [Facelets] [3],
  • en [4], deux fichiers ont été créés : [index.xhtml] et [WEB-INF / web.xml].

Le fichier [] configure l'application JSF. C'est le suivant :

[web.xml]
CacherSélectionnez

Nous avons déjà commenté ce fichier en page  . Rappelons ses principales propriétés :

  • toutes les URL du type faces/* sont traitées par la servlet [javax.faces.webapp.FacesServlet],
  • la page [index.xhtml] est la page d'accueil de l'application.

Le fichier [index.xhtml] créé est le suivant :

[index.xhtml]
CacherSélectionnez

Nous avons déjà rencontré ce fichier page .

Créons maintenant une classe Java :

Image non disponible
  • en [1], on crée une classe Java dans la branche [Source Packages],
  • en [2], on lui donne un nom et on la met dans un package [3],
  • en [4], la classe créée apparaît dans le projet.

Le code de la classe créée est un squelette de classe :

 
CacherSélectionnez

Enfin, créons un fichier de messages :

Image non disponible
  • en [1], création d'un fichier [Properties],
  • en [2], on donne le nom du fichier et en [3] son dossier,
  • en [4], le fichier [messages.properties] a été créé.

Parfois, il est nécessaire de créer le fichier [WEB-INF/faces-config.xml] pour configurer le projet JSF. Ce fichier était obligatoire avec JSF 1. Il est facultatif avec JSF 2. Il est cependant nécessaire si le site JSF est internationalisé. Ce sera le cas par la suite. Aussi montrons-nous maintenant comment créer ce fichier de configuration.

  • Image non disponible

    en [1], nous créons le fichier de configuration JSF,

  • en [2], on donne son nom et en [3] son dossier,
  • en [4], le fichier créé.

Le fichier [faces-config.xml] créé est le suivant :

[faces-config.xml]
CacherSélectionnez

La balise racine est <faces-config>. Le corps de cette balise est vide. Nous serons amenés à le compléter.

Nous avons désormais tous les éléments pour créer un projet JSF. Dans les exemples qui vont suivre, nous présentons le projet JSF complet et nous en détaillons ensuite les éléments un à un. Nous présentons maintenant un projet pour expliquer les notions :

  • de gestionnaire d'événements d'un formulaire,
  • d'internationalisation des pages d'un site JSF,
  • de navigation entre pages.

Le projet [mv-jsf2-02] devient le suivant. Le lecteur peut le trouver sur le site des exemples (cf paragraphe , page ).

Image non disponible
  • en [1], des fichiers de configuration du projet JSF,
  • en [2], les pages JSF du projet,
  • en [3], l'unique classe Java,
  • en [4], les fichiers de messages.

III-D-3. La page [index.xhtml]

Le fichier [index.xhtml] [1] envoie la page [2] au navigateur client :

Image non disponible

Le code qui produit cette page est le suivant :

[index.xhtml]
CacherSélectionnez
  • lignes 7-9 : les espaces de noms / bibliothèques de balises utilisées par la page. Les balises préfixées par h sont des balises HTML alors que les balises préfixées par f sont des balises propres à JSF,
  • ligne 10 : la balise <f:view> sert à délimiter le code que le moteur JSF doit traiter, celui où apparaîssent les balises <f:xx>. L'attribut locale permet de préciser une langue d'affichage pour la page. Ici, nous en utiliserons deux, l'anglais et le français. La valeur de l'attribut locale est exprimée sous la forme d'une expression EL (Expression Language) #{expression}. La forme de expression peut être diverse. Nous l'exprimerons le plus souvent sous la forme bean['clé'] ou bean.champ. Dans nos exemples, bean sera soit une classe Java soit un fichier de messages. Avec JSF 1, ces beans devaient être déclarés dans le fichier [faces-config.xml]. Avec JSF 2, ce n'est plus obligatoire pour les classes Java. On peut désormais utiliser des annotations qui font d'une classe Java, un bean connu de JSF 2. Le fichier des messages doit lui être déclaré dans le fichier de configuration [faces-config.xml].

III-D-4. Le bean [changeLocale]

Dans l'expression EL #{changeLocale.locale}" :

  • changeLocale est le nom d'un bean, ici la classe Java ChangeLocale,
  • locale est un champ de la classe ChangeLocale. L'expression est évaluée par [ChangeLocale].getLocale(). De façon générale l'expression #{bean.champ} est évaluée comme [Bean].getChamp(), où [Bean] est une instance de la classe Java à qui on a attribué le nom bean et getChamp, le getter associé au champ champ du bean.

La classe ChangeLocale est la suivante :

[ ChangeLocale]
CacherSélectionnez
  • ligne 11 : le champ locale,
  • ligne 17 : son getter,
  • ligne 7 : l'annotation ManagedBean fait de la classe Java ChangeLocale un bean reconnu par JSF. Un bean est identifié par un nom. Celui-ci peut être fixé par l'attribut name de l'annotation : @ManagedBean(name= "xx "). En l'absence de l'attribut name, le nom de la classe est utilisé en passant son premier caractère en minuscule. Le nom du bean ChangeLocale est donc changeLocale. On prêtera attention au fait que l'annotation ManagedBean appartient au package javax.faces.bean.ManagedBean et non au package javax.annotations.ManagedBean.
  • ligne 8 : l'annotation SessionScoped fixe la portée du bean. Il y en a plusieurs. Nous utiliserons couramment les trois suivantes :
  • RequestScoped : la durée de vie du bean est celle du cycle demande navigateur / réponse serveur. Si pour traiter une nouvelle requête du même navigateur ou d'un autre, ce bean est de nouveau nécessaire, il sera instancié de nouveau,
  • SessionScoped : la durée de vie du bean est celle de la session d'un client particulier. Le bean est créé initialement pour les besoins de l'une des requêtes de ce client. Il restera ensuite en mémoire dans la session de ce client. Un tel bean mémorise en général des données propres à un client donné. Il sera détruit lorsque la session du client sera détruite,
  • ApplicationScoped : la durée de vie du bean est celle de l'application elle-même. Un bean avec cette durée de vie est le plus souvent partagé par tous les clients de l'application. Il est en général initialisé au début de l'application.

Ces annotations existent dans deux packages : javax.enterprise.context et javax.faces.bean. Avec l'annotation @ManagedBean il faut utiliser ce dernier package. Les annotations du package javax.enterprise.context peuvent elles être utilisées en combinaison avec l'annotation @Named (au lieu de @ManagedBean). Elles nécessitent un conteneur Java EE6 et sont donc plus exigeantes. On notera que la classe [ChangeLocale] implémente l'interface [Serializable]. C'est obligatoire pour les beans de portée Session que le serveur web peut être amené à sérialiser dans des fichiers. Nous reviendrons ultérieurement sur le bean [ChangeLocale].

III-D-5. Le fichier des messages

Revenons au fichier [index.xhtml] :

[index.xhtml]
CacherSélectionnez
  • ligne 8 : la balise <h:outputText> affiche la valeur d'une expression EL #{msg['welcome.titre']} de la forme #{bean['champ']}. bean est soit le nom d'une classe Java soit celui d'un fichier de messages. Ici, c'est celui d'un fichier de messages. Ce dernier doit être déclaré dans le fichier de configuration [faces-config.xml]. Le bean msg est déclaré comme suit :
[faces-config.xml]
CacherSélectionnez
  • lignes 11-18 : la balise <application> sert à configurer l'application JSF,
  • lignes 12-17 : la balise <resource-bundle> sert à définir des ressources pour l'application, ici un fichier de messages,
  • lignes 13-15 : la balise <base-name> définit le nom du fichier de messages,
  • ligne 14 : le fichier s'appellera [_CodeLangue][_CodePays].properties. La balise <base-name> ne définit que la première partie du nom. Le reste est implicite. Il peut exister plusieurs fichiers de messages, un par langue :
Image non disponible
  • en [1], on voit quatre fichiers de messages correspondant au nom de base messages défini dans [faces-config.xml],
  • messages_fr.properties : contient les messages en français (code fr) ;
  • messages_en.properties : contient les messages en anglais (code en) ;
  • messages_es_ES.properties : contient les messages en espagnol (code es) de l'Espagne (code ES). Il existe d'autres types d'espagnol, par exemple celui de Bolivie (es_BO) ;
  • messages.properties : est utilisé par le serveur lorsque la langue de la machine sur laquelle il s'exécute n'a aucun fichier de messages qui lui est associé. Il serait utilisé par exemple, si l'application s'exécutait sur une machine en Allemagne où la langue par défaut serait l'allemand (de). Comme il n'existe pas de fichier [messages_de.properties], l'application utiliserait le fichier [messages.properties],
  • en [2] : les codes des langues font l'objet d'un standard international,
  • en [3] : idem pour les codes des pays.

Le nom du fichier des messages est défini ligne 14. Il sera cherché dans le Classpath du projet. S'il est à l'intérieur d'un paquetage, celui-ci doit être défini ligne 14, par exemple ressources.messages, si le fichier [messages.properties] se trouve dans le dossier [ressources] du Classpath. Le nom, ligne 14, ne comportant pas de paquetage, le fichier [messages.properties] doit être placé à la racine du dossier [src / main / resources] :

Image non disponible

En [1], dans l'onglet [Projects] du projet Netbeans, le fichier [messages.properties] est présenté comme une liste des différentes versions de messages définies. Les versions sont identifiées par une suite d'un à trois codes [codeLangue_codePays_codeVariante]. En [1], seul le code [codeLangue] a été utilisé : en pour l'anglais, fr pour le français. Chaque version fait l'objet d'un fichier séparé dans le système de fichiers.

Dans notre exemple, le fichier de messages en français [messages_fr.properties] contiendra les éléments suivants :

[messages_fr.properties]
CacherSélectionnez

Le fichier [messages_en.properties] lui, sera le suivant :

[messages_en.properties]
CacherSélectionnez

Le fichier [messages.properties] est identique au fichier [messages_en.properties]. Au final, le navigateur client aura le choix entre des pages en Français et des pages en Anglais.

Revenons au fichier [faces-config.xml] qui déclare le fichier des messages :

[faces-config.xml]
CacherSélectionnez

La ligne 8 indique qu'une ligne du fichier des messages sera référencée par l'identificateur msg dans les pages JSF. Cet identificateur est utilisé dans le fichier [index.xhtml] étudié :

[index.xhtml]
CacherSélectionnez

La balise <h:outputText> de la ligne 8, va afficher la valeur du message (présence de l'identificateur msg) de clé welcome.titre. Ce message est cherché et trouvé dans le fichier [messages.properties] de la langue active du moment. Par exemple, pour le français :

 
CacherSélectionnez

Un message est de la forme clé=valeur. La ligne 8 du fichier [index.xhtml] devient la suivante après évaluation de l'expression #{msg['welcome.titre']} :

 
CacherSélectionnez

Ce mécanisme des fichiers de messages permet de changer facilement la langue des pages d'un projet JSF. On parle d'internationalisation du projet ou plus souvent de son abréviation i18n, parce que le mot internationalisation commence par i et finit par n et qu'il y a 18 lettres entre le i et le n.

III-D-6. Le formulaire

Continuons à explorer le contenu du fichier [index.xhtml] :

[index.xhtml]
CacherSélectionnez
  • lignes 11-18 : la balise <h:form> introduit un formulaire. Un formulaire est généralement constitué de :
  • balises de champs de saisie (texte, boutons radio, cases à cocher, listes d'éléments…) ;
  • balises de validation du formulaire (boutons, liens). C'est via un bouton ou un lien que l'utilisateur envoie ses saisies au serveur qui les traitera,

Toute balise JSF peut être identifiée par un attribut id. Le plus souvent, on peut s'en passer et c'est ce qui a été fait dans la plupart des balises JSF utilisées ici. Néanmoins, cet attribut est utile dans certains cas. Ligne 17, le formulaire est identifié par l'id formulaire. Dans cet exemple, l'id du formulaire ne sera pas utilisé et aurait pu être omis.

  • lignes 18-21 : la balise <h:panelGrid> définit ici un tableau HTML à deux colonnes. Elle donne naissance à la balise HTML <table>,

Dans les deux cas, la page JSF est affichée, une fois que l'action définie par l'attribut action a été exécutée.

Examinons la mécanique du traitement des formulaires avec l'exemple du lien de la ligne 13 :

 
CacherSélectionnez

Tout d'abord, le fichier des messages est exploité pour remplacer l'expression #{msg['welcome.langue1']} par sa valeur. Après évaluation, la balise devient :

 
CacherSélectionnez

La traduction HTML de cette balise JSF va être la suivante :

 
CacherSélectionnez

ce qui donnera l'apparence visuelle qui suit :

Image non disponible

On notera l'attribut onclick de la balise HTML <a>. Lorsque l'utilisateur va cliquer sur le lien [Français], du code Javascript va être exécuté. Celui-ci est embarqué dans la page que le navigateur a reçue et c'est le navigateur qui l'exécute. Le code Javascript est largement utilisé dans les technologies JSF et AJAX (Asynchronous Javascript And Xml). Il a en général pour but d'améliorer l'ergonomie et la réactivité des applications web. Il est le plus souvent généré de façon automatique par des outils logiciels et il n'est pas alors utile de le comprendre. Mais parfois un développeur peut être amené à ajouter du code Javascript dans ses pages JSF. La connaissance de Javascript est alors nécessaire.

Il est inutile ici de comprendre le code Javascript généré pour la balise JSF <h:commandLink>. On peut cependant noter deux points :

  • le code Javascript utilise l'identifiant formulaire que nous avons donné à la balise JSF <h:form>,
  • JSF génère des identifiants automatiques pour toutes les balises où l'attribut id n'a pas été défini. On en voit un exemple ici : j_idt8. Donner un identifiant clair aux balises permet de mieux comprendre le code Javascript généré si cela devient nécessaire. C'est notamment le cas lorsque le développeur doit lui-même ajouter du code Javascript qui manipule les composants de la page. Il a alors besoin de connaître les identifiants id de ses composants.

Que va-t-il se passer lorsque l'utilisateur va cliquer sur le lien [Français] de la page ci-dessus ? Considérons l'architecture d'une application JSF :

Image non disponible

Le contrôleur [Faces Servlet] va recevoir la requête du navigateur client sous la forme HTTP suivante :

 
CacherSélectionnez
  • lignes 1-2 : le navigateur demande l'URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. C'est toujours ainsi : les saisies faites dans un formulaire JSF initialement obtenu avec l'URL URLFormulaire sont envoyées à cette même URL. Le navigateur a deux moyens pour envoyer les valeurs saisies : GET et POST. Avec la méthode GET, les valeurs saisies sont envoyées par le navigateur dans l'URL qui est demandée. Ci-dessus, le navigateur aurait pu envoyer la première ligne suivante :
 
CacherSélectionnez

Avec la méthode POST utilisée ici, le navigateur envoie au serveur les valeurs saisies au moyen de la ligne 6.

  • ligne 3 : indique la forme d'encodage des valeurs du formulaire,
  • ligne 4 : indique la taille en octets de la ligne 6,
  • ligne 5 : ligne vide qui indique la fin des entêtes HTTP et le début des 126 octets des valeurs du formulaire,
  • ligne 6 : les valeurs du formulaire sous la forme element1=valeur1&element2=valeur2& …, la forme d'encodage définie par la ligne 3. Dans cette forme de codage, certains caractères sont remplacés par leur valeur hexadécimale. C'est le cas dans le dernier élément :
 
CacherSélectionnez

où %3A représente le caractère :. C'est donc la chaîne formulaire:j_idt8=formulaire:j_idt8 qui est envoyée au serveur. On se rappelle peut-être que nous avons déjà rencontré l'identifiant j_idt8 lorsque nous avons examiné le code HTML généré pour la balise

 
CacherSélectionnez

Il avait été généré de façon automatique par JSF. Ce qui nous importe ici, c'est que la présence de cet identifiant dans la chaîne des valeurs envoyées par le navigateur client permet à JSF de savoir que le lien [Français] a été cliqué. Il va alors utiliser l'attribut action ci-dessus, pour décider comment traiter la chaîne reçue. L'attribut action="#{changeLocale.setFrenchLocale}" indique à JSF que la requête du client doit être traitée par la méthode [setFrenchLocale] d'un objet appelé changeLocale. On se rappelle que ce bean a été défini par des annotations dans la classe Java [ChangeLocale] :

[ChangeLocale]
CacherSélectionnez

Le nom d'un bean est défini par l'attribut name de l'annotation @ManagedBean. En l'absence de cet attribut, c'est le nom de la classe qui est utilisé comme nom de bean avec le premier caractère mis en minuscule.

Revenons à la requête du navigateur :

Image non disponible

et à la balise <h:commandLink> qui a généré le lien [Français] sur lequel on a cliqué :

 
CacherSélectionnez

Le contrôleur va transmettre la requête du navigateur au gestionnaire d'événements défini par l'attribut action de la balise <h:commandLink>. Le gestionnaire d'événements M référencé par l'attribut action d'une commande <h:commandLink> doit avoir la signature suivante :

 
CacherSélectionnez
  • il ne reçoit aucun paramètre. Nous verrons qu'il peut néanmoins avoir accès à la requête du client,
  • il doit rendre un résultat C de type String. Cette chaîne de caractères C peut être :
  • soit le nom d'une page JSF du projet ;
  • soit un nom défini dans les règles de navigation du fichier [faces-config.xml] et associé à une page JSF du projet ;
  • soit un pointeur null, si le navigateur client ne doit pas changer de page,

Dans l'architecture JSF ci-dessus, le contrôleur [Faces Servlet] utilisera la chaîne C rendue par le gestionnaire d'événements et éventuellement son fichier de configuration [faces-config.xml] pour déterminer quelle page JSF, il doit envoyer en réponse au client [4].

Dans la balise

 
CacherSélectionnez

le gestionnaire de l'événement clic sur le lien [Français] est la méthode [changeLocale.setFrenchLocale] où changeLocale est une instance de la classe [] déjà étudiée :

[ChangeLocale]
CacherSélectionnez

La méthode setFrenchLocale a bien la signature des gestionnaires d'événements. Rappelons-nous que le gestionnaire d'événements doit traiter la requête du client. Puisqu'il ne reçoit pas de paramètres, comment peut-il avoir accès à celle-ci ? Il existe diverses façons de faire :

  • le bean B qui contient le gestionnaire d'événements de la page JSF P est aussi souvent celui qui contient le modèle M de cette page. Cela signifie que le bean B contient des champs qui seront initialisés par les valeurs saisies dans la page P. Cela sera fait par le contrôleur [Faces Servlet] avant que le gestionnaire d'événements du bean B ne soit appelé. Ce gestionnaire aura donc accès, via les champs du bean B auquel il appartient, aux valeurs saisies par le client dans le formulaire et pourra les traiter.
  • la méthode statique [FacesContext.getCurrentInstance()] de type [FacesContext] donne accès au contexte d'exécution de la requête JSF courante qui est un objet de type [FacesContext]. Le contexte d'exécution de la requête ainsi obtenu, permet d'avoir accès aux paramètres postés au serveur par le navigateur client avec la méthode suivante :
 
CacherSélectionnez

Si les paramètres postés (POST) par le navigateur client sont les suivants :

 
CacherSélectionnez

la méthode getRequestParameterMap() rendra le dictionnaire suivant :

clé valeur
formulaire formulaire
javax.faces.ViewState
formulaire:j_id_id21 formulaire:j_id_id21

Dans la balise

 
CacherSélectionnez

qu'attend-on du gestionnaire d'événements locale.setFrenchLocale ? On veut qu'il fixe la langue utilisée par l'application. Dans le jargon Java, on appelle cela " localiser " l'application. Cette localisation est utilisée par la balise <f:view> de la page JSF [index.xhtml] :

 
CacherSélectionnez

Pour passer la page en Français, il suffit que l'attribut locale ait la valeur fr. Pour la passer en anglais, il faut lui donner la valeur en. La valeur de l'attribut locale est obtenue par l'expression [ChangeLocale].getLocale(). Cette expression rend la valeur du champ locale de la classe [ChangeLocale]. On en déduit le code de la méthode [ChangeLocale].setFrenchLocale() qui doit passer les pages en Français :

 
CacherSélectionnez

Nous avons expliqué qu'un gestionnaire d'événements devait rendre une chaîne de caractères C qui sera utilisée par [Faces Servlet] afin de trouver la page JSF à envoyer en réponse au navigateur client. Si la page à renvoyer est la même que celle en cours de traitement, le gestionnaire d'événements peut se contenter de renvoyer la valeur null. C'est ce qui est fait ici ligne 3 : on veut renvoyer la même page [index.xhtml] mais dans une langue différente.

Revenons à l'architecture de traitement de la requête :

Image non disponible

Le gestionnaire d'événements changeLocale.setFrenchLocale a été exécuté et a rendu la valeur null au contrôleur [Faces Servlet]. Celui-ci va donc réafficher la page [index.xhtml]. Revoyons celle-ci :

 
CacherSélectionnez

A chaque fois qu'une valeur de type #{msg['…']} est évaluée, l'un des fichiers des messages [messages.properties] est utilisé. Celui utilisé est celui qui correspond à la " localisation " de la page (ligne 6). Le gestionnaire d'événements changeLocale.setFrenchLocale définissant cette localisation à fr, c'est le fichier [messages_fr.properties] qui sera utilisé. Un clic sur le lien [Anglais] (ligne 14) changera la localisation en en (cf méthode changeLocale.setEnglishLocale). Ce sera alors le fichier [messages_en.properties] qui sera utilisé et la page apparaîtra en anglais :

Image non disponible

A chaque fois que la page [index.xhtml] est affichée, la balise <f:view> est exécutée :

 
CacherSélectionnez

et donc la méthode [ChangeLocale].getLocale() est réexécutée. Comme nous avons donné la portée Session à notre bean :

[ChangeLocale]
CacherSélectionnez

la localisation faite lors d'une requête est conservée pour les requêtes suivantes.

Il nous reste un dernier élément de la page [index.xhtml] à étudier :

 
CacherSélectionnez

La balise <h:commandLink> de la ligne 17, a un attribut action égal à une chaîne de caractères. Dans ce cas, aucun gestionnaire d'événements n'est appelé pour traiter la page. On passe tout de suite à la page [page1.xhtml]. Examinons le fonctionnement de l'application dans ce cas d'utilisation :

Image non disponible

L'utilisateur clique sur le lien [Page 1]. Le formulaire est posté au contrôleur [Faces Servlet]. Celui reconnaît dans la requête qu'il reçoit, le fait que le lien [Page 1] a été cliqué. Il examine la balise correspondante :

 
CacherSélectionnez

Il n'y a pas de gestionnaire d'événements associé au lien. Le contrôleur [Faces Servlet] passe tout de suite à l'étape [3] ci-dessus et affiche la page [page1.xhtml] :

Image non disponible

III-D-7. La page JSF [page1.xhtml]

La page [page1.xhtml] envoie le flux suivant au navigateur client :

Image non disponible

Le code qui produit cette page est le suivant :

[page1.xhtml]
CacherSélectionnez

Il n'y a dans cette page rien qui n'ait déjà été expliqué. Le lecteur fera la correspondance entre le code JSF et la page envoyée au navigateur client. Le lien de retour à la page d'accueil :

 
CacherSélectionnez

fera afficher la page [index.xhtml].

III-D-8. Exécution du projet

Notre projet est désormais complet. Nous pouvons le construire (Clean and Build) :

Image non disponible
  • la construction du projet crée dans l'onglet [Files] le dossier [target]. Dans celui-ci, on trouve l'archive [mv-jsf2-02-1.0-SNAPSHOT.war] du projet. C'est cette archive qui est déployée sur le serveur,
  • dans [WEB-INF / classes] [2], on trouve les classes compilées du dossier [Source Packages] du projet ainsi que les fichiers qui se trouvaient dans la branche [Other Sources], ici les fichiers de messages,
  • dans [WEB-INF / lib] [3], on trouve les bibliothèques du projet,
  • à la racine de [WEB-INF] [4], on trouve les fichiers de configuration du projet,
    Image non disponible
  • à la racine de l'archive [5], on trouve les pages JSF qui étaient dans la branche [Web Pages] du projet,
  • une fois le projet construit, il peut être exécuté [6]. Il va être exécuté selon sa configuration d'exécution [7],
  • le serveur Tomcat va être lancé s'il n'était pas déjà lancé [8],
  • l'archive [mv-jsf2-02-1.0-SNAPSHOT.war] va être chargée sur le serveur. On appelle cela le déploiement du projet sur le serveur d'application,
  • en [9], il est demandé de lancer un navigateur à l'exécution. Celui-ci va demander le contexte de l'application [10], c.a.d. l'URL [http://localhost:8080/mv-jsf2-02]. D'après les règles du fichier [web.xml] (cf page ), c'est le fichier [faces/index.xhtml] qui va être servi au navigateur client. Puisque l'URL est de la forme [/faces/*], elle va être traitée par le contrôleur [Faces Servlet] (cf [web.xml] page ). Celui-ci va traiter la page et envoyer le flux HTML suivant :
Image non disponible
  • le contrôleur [Faces Servlet] traitera alors les événements qui vont se produire à partir de cette page.

III-D-9. Le fichier de configuration [faces-config.xml]

Nous avons utilisé le fichier [faces-config.xml] suivant :

[faces-config.xml]
CacherSélectionnez

C'est le fichier minimal pour une application JSF 2 internationalisée. Nous avons utilisé ici des possibilités nouvelles de JSF 2 vis à vis de JSF 1 :

  • déclarer des beans et leur portée avec les annotations @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
  • naviguer entre des pages en utilisant comme clés de navigation, les noms des pages XHTML sans leur suffixe xhtml.

On peut vouloir ne pas utiliser ces possibilités et déclarer ces éléments du projet JSF dans [faces-config.xml] comme dans JSF 1. Dans ce cas, le fichier [faces-config.xml] pourrait être le suivant :

[faces-config.xml]
CacherSélectionnez
  • lignes 20-24 : déclaration du bean changeLocale :
  • ligne 21 : nom du bean ;
  • ligne 22 : nom complet de la classe associée au bean ;
  • ligne 23 : portée du bean. Les valeurs possibles sont request, session, application,
  • lignes 27-34 : déclaration d'une règle de navigation :
  • ligne 28 : on peut décrire la règle. Ici, on ne l'a pas fait ;
  • ligne 29 : la page à parir de laquelle on navigue (point de départ) ;
  • lignes 30-33 : un cas de navigation. Il peut y en avoir plusieurs ;
  • ligne 31 : la clé de navigation ;
  • ligne 32 : la page vers laquelle on navigue.

Les règles de navigation peuvent être affichées d'une façon plus visuelle. Lorsque le fichier [faces-config.xml] est édité, on peut utiliser l'onglet [PageFlow] :

Image non disponible

Supposons que nous utilisions le fichier [faces-config.xml] précédent. Comment évoluerait notre application ?

  • dans la classe [ChangeLocale], les annotations @ManagedBean et @SessionScoped disparaîtraient puisque désormais le bean est déclaré dans [faces-config],
  • la navigation de [index.xhtml] vers [page1.xhtml] par un lien deviendrait :

    A l'attribut action, on affecte la clé de navigation p1 définie dans [faces-config],

     
    CacherSélectionnez
  • la navigation de [page1.xhtml] vers [index.xhtml] par un lien deviendrait :
 
CacherSélectionnez

A l'attribut action, on affecte la clé de navigation welcome définie dans [faces-config],

  • les méthodes setFrenchLocale et setEnglishLocale qui doivent rendre une clé de navigation, n'ont pas à être modifiées car elles rendaient null pour indiquer qu'on restait sur la même page.

III-D-10. Conclusion

Revenons sur le projet Netbeans que nous avons écrit :

Image non disponible

Ce projet recouvre l'architecture suivante :

Image non disponible

Dans chaque projet JSF, nous trouverons les éléments suivants :

  • des pages JSF [A] qui sont envoyées [4] aux navigateurs clients par le contrôleur [Faces Servlet] [3],
  • des fichiers de messages [C] qui permettent de changer la langue des pages JSF,
  • des classes Java [B] qui traitent les événements qui se produisent sur le navigateur client [2a, 2b] et / ou qui servent de modèles aux pages JSF [3]. Le plus souvent, les couches [métier] et [DAO] sont développées et testées séparément. La couche [web] est alors testée avec une couche [métier] fictive. Si les couches [métier] et [DAO] sont disponibles, on travaille le plus souvent avec leurs archives .jar.
  • des fichiers de configuration [D] pour lier ces divers éléments entre-eux. Le fichier [web.xml] a été décrit page , et sera peu souvent modifié. De même pour [faces-config] où nous utiliserons toujours la version simplifiée.

III-E. Exemple mv-jsf2-03 : formulaire de saisie - composants JSF

A partir de maintenant, nous ne montrerons plus la construction du projet. Nous présentons des projets tout faits et nous en expliquons le fonctionnement. Le lecteur peut récupérer l'ensemble des exemples sur le site de ce document (cf paragraphe ).

III-E-1. L'application

L'application a une unique vue :

Image non disponible

L'application présente les principaux composants JSF utilisables dans un formulaire de saisies :

  • la colonne [1] indique le nom de la balise JSF / HTML utilisée,
  • la colonne [2] présente un exemple de saisie pour chacune des balises rencontrées,
  • la colonne [3] affiche les valeurs du bean servant de modèle à la page,
  • les saisies faites en [2] sont validées par le bouton [4]. Cette validation ne fait que mettre à jour le bean modèle de la page. La même page est ensuite renvoyée. Aussi après validation, la colonne [3] présente-t-elle les nouvelles valeurs du bean modèle permettant ainsi à l'utilisateur de vérifier l'impact de ses saisies sur le modèle de la page.

III-E-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible
  • en [1], les fichiers de configuration du projet JSF,
  • en [2], l'unique page du projet : index.xhtml,
  • en [3], une feuille de style [styles.css] pour configurer l'aspect de la page [index.xhtml]
  • en [4], les classes Java du projet,
  • en [5], le fichier des messages de l'application en deux langues : français et anglais.

III-E-3. Le fichier [pom.xml]

Nous ne présentons que les dépendances :

[pom.xml]
CacherSélectionnez

Ce sont les dépendances nécessaires à un projet JSF. Dans les exemples qui suivront, ce fichier ne sera présenté que lorsqu'il changera.

III-E-4. Le fichier [web.xml]

Le fichier [web.xml] a été configuré pour que la page [index.xhtml] soit la page d'accueil du projet :

[web.xml]
CacherSélectionnez
  • ligne 30 : la page [index.xhtml] est page d'accueil,
  • lignes 11-14 : un paramètre pour la servlet [Faces Servlet]. Elle demande à ce que les commentaires dans une facelet du genre 
    <!-- langues -->

soient ignorés. Sans ce paramètre, les commentaires posent des problèmes peu compréhensibles,

  • lignes 3-6 : un paramètre pour la servlet [Faces Servlet] qui sera expliqué un peu plus loin.

III-E-5. Le fichier [faces-config.xml]

Le fichier [faces-config.xml] de l'application est le suivant :

[faces-config.xml]
CacherSélectionnez
  • lignes 11-16 : configurent le fichier des messages de l'application.

III-E-6. Le fichier des messages [messages.properties]

Les fichiers des messages (cf [5] dans la copie d'écran du projet) sont les suivants :

[messages_fr.properties]

 
CacherSélectionnez

Ces messages sont affichés aux endroits suivants de la page :

Image non disponible

La version anglaise des messages est la suivante :

[messages_en.properties]

 
CacherSélectionnez

III-E-7. Le modèle [Form.java] de la page [index.xhtml]

Image non disponible

Dans le projet ci-dessus, la classe [Form.java] va servir de modèle ou backing bean à la page JSF [index.xhtml]. Illustrons cette notion de modèle avec un exemple tiré de la page [index.xhtml] :

[index.xhtml]
CacherSélectionnez

A la demande initiale de la page [index.xhtml], le code ci-dessus génère la ligne 2 du tableau des saisies :

Image non disponible

La ligne 2 affiche la zone [1], les lignes 3-6 : la zone [2], la ligne 7 : la zone [3].

Les lignes 5 et 7 utilisent une expression EL faisant intervenir le bean form défini dans la classe [Form.java] de la façon suivante :

[Form.java]
CacherSélectionnez
  • la ligne 7 définit un bean sans nom. Celui-ci sera donc le nom de la classe commençant par une minuscule : form,
  • le bean est de portée request. Cela signifie que dans un cycle demande client / réponse serveur, il est instancié lorsque la requête en a besoin et supprimé lorsque la réponse au client a été rendue.

Dans le code ci-dessous de la page [index.xhtml] :

 
CacherSélectionnez

les lignes 5 et 7 utilisent la valeur inputText du bean form. Pour comprendre les liens qui unissent une page P à son modèle M, il faut revenir au cycle qui caractérise une application web :

Image non disponible

Il faut distinguer le cas où la page P est envoyée en réponse au navigateur (étape 4), par exemple lors de la demande initiale de la page, du cas où l'utilisateur ayant provoqué un événement sur la page P, celui-ci est traité par le contrôleur [Faces Servlet] (étape 1).

On peut distinguer ces deux cas en les regardant du point de vue du navigateur :

  1. lors de la demande initiale de la page, le navigateur fait une opération GET sur l'URL de la page,
  2. lors de la soumission des valeurs saisies dans la page, le navigateur fait une opération POST sur l'URL de la page.

Dans les deux cas, c'est la même URL qui est demandée. Selon la nature de la demande GET ou POST du navigateur, le traitement de la requête va différer.

[cas 1 - demande initiale de la page P]

Le navigateur demande l'URL de la page avec un GET. Le contrôleur [Faces Servlet] va passer directement à l'étape [4] de rendu de la réponse et la page [index.xhtml] va être envoyée au client. Le contrôleur JSF va demander à chaque balise de la page de s'afficher. Prenons l'exemple de la ligne 5 du code de [index.xhtml] :

[index.xhtml]
CacherSélectionnez

La balise JSF <h:inputText value="valeur"/> donne naissance à la balise HTML <input type="text" value="valeur"/>. La classe chargée de traiter cette balise rencontre l'expression #{form.inputText} qu'elle doit évaluer :

  • si le bean form n'existe pas encore, il est créé par instanciation de la classe forms.Form,
  • l'expression #{form.inputText} est évaluée par appel à la méthode form.getInputText(),
  • le texte <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /> est inséré dans le flux HTML qui va être envoyé au client si on imagine que la méthode form.getInputText() a rendu la chaîne "texte". JSF va par ailleurs donner un nom (name) au composant HTML mis dans le flux. Ce nom est construit à partir des identifiants id du composant JSF analysé et ceux de ses composants parents, ici la balise <h:form id="formulaire"/>.

On retiendra que si dans une page P, on utilise l'expression #{M.champ}M est le bean modèle de la page P, celui-ci doit disposer de la méthode publique getChamp(). Le type rendu par cette méthode doit pouvoir être converti en type String. Un modèle M possible et fréquent est le suivant :

 
CacherSélectionnez

T est un type qui peut être converti en type String, éventuellement avec une méthode toString.

Toujours dans le cas de l'affichage de la page P, le traitement de la ligne :

 
CacherSélectionnez

sera analogue et le flux HTML suivant sera créé :

 
CacherSélectionnez

De façon interne au serveur, la page P est représentée comme un arbre de composants, image de l'arbre des balises de la page envoyée au client. Nous appellerons vue ou , cet arbre. Cet état est mémorisé. Il peut l'être de deux façons selon une configuration faite dans le fichier [web.xml] de l'application :

[web.xml]
CacherSélectionnez

Les lignes 7-11 définissent le contrôleur [Faces Servlet]. Celui-ci peut être configuré par différentes balises <context-param> dont celle des lignes 3-6 qui indique que l'état d'une page doit être sauvegardé sur le client (le navigateur). L'autre valeur possible, ligne 5, est server pour indiquer une sauvegarde sur le serveur. C'est la valeur par défaut.

Lorsque l'état d'une page est sauvegardé sur le client, le contrôleur JSF ajoute à chaque page HTML qu'il envoie, un champ caché dont la valeur est l'état actuel de la page. Ce champ caché a la forme suivante :

 
CacherSélectionnez

Sa valeur représente sous forme codée, l'état de la page envoyée au client. Ce qu'il est important de comprendre, c'est que ce champ caché fait partie du formulaire de la page et fera donc partie des valeurs postées par le navigateur lors de la validation du formulaire. A partir de ce champ caché, le contrôleur JSF est capable de restaurer la vue telle qu'elle a été envoyée au client.

Lorsque l'état d'une page est sauvegardé sur le serveur, l'état de la page envoyée au client est sauvegardé dans la session de celui-ci. Lorsque le navigateur client va poster les valeurs saisies dans le formulaire, il va envoyer également son jeton de session. A partir de celui-ci, le contrôleur JSF retrouvera l'état de la page envoyée au client et la restaurera.

L'état d'une page JSF peut nécessiter plusieurs centaines d'octets pour être codé. Comme cet état est maintenu pour chaque utilisateur de l'application, on peut rencontrer des problèmes mémoire s'il y a un grand nombre d'utilisateurs. Pour cette raison, nous avons choisi ici de sauvegarder l'état de la page sur le client (cf [web.xml] paragraphe , page ).

[cas 2 - traitement de la page P]

Image non disponible

On est à l'étape [1] ci-dessus où le contrôleur [Faces Servlet] va recevoir une requête POST du navigateur client à qui il a envoyé précédemment la page [index.xhtml]. On est en présence du traitement d'un événement de la page. Plusieurs étapes vont se dérouler avant même que l'événement ne puisse être traité en [2a]. Le cycle de traitement d'une requête POST par le contrôleur JSF est le suivant :

Image non disponible
  • en [A], grâce au champ caché javax.faces.ViewState la vue initialement envoyée au navigateur client est reconstituée. Ici, les composants de la page retrouvent la valeur qu'ils avaient dans la page envoyée. Notre composant inputText retrouve sa valeur "texte",
  • en [B], les valeurs postées par le navigateur client sont utilisées pour mettre à jour les composants de la vue. Ainsi si dans le champ de saisie HTML nommé inputText, l'utilisateur a tapé "jean", la valeur "jean" remplace la valeur "texte". Désormais la vue reflète la page telle que l'a modifiée l'utilisateur et non plus telle qu'elle a été envoyée au navigateur,
  • en [C], les valeurs postées sont vérifiées. Supposons que le composant inputText précédent soit le champ de saisie d'un âge. Il faudra que la valeur saisie soit un nombre entier. Les valeurs postées par le navigateur sont toujours de type String. Leur type final dans le modèle M associé à la page P peut être tout autre. Il y a alors conversion d'un type String vers un autre type T. Cette conversion peut échouer. Dans ce cas, le cycle demande / réponse est terminé et la page P construite en [B] est renvoyée au navigateur client avec des messages d'erreur si l'auteur de la page P les a prévus. On notera que l'utilisateur retrouve la page telle qu'il l'a saisie, sans effort de la part du développeur. Dans une autre technologie, telle que JSP, le développeur doit reconstruire lui-même la page P avec les valeurs saisies par l'utilisateur. La valeur d'un composant peut subir également un processus de validation. Toujours avec l'exemple du composant inputText qui est le champ de saisie d'un âge, la valeur saisie devra être non seulement un nombre entier mais un nombre entier compris dans un intervalle [1,N]. Si la valeur saisie passe l'étape de la conversion, elle peut ne pas passer l'étape de la validation. Dans ce cas, là également le cycle demande / réponse est terminé et la page P construite en [B] est renvoyée au navigateur client,
  • en [D], si tous les composants de la page P passent l'étape de conversion et de validation, leurs valeurs vont être affectées au modèle M de la page P. Si la valeur du champ de saisie généré à partir de la balise suivante :
 
CacherSélectionnez

est "jean", alors cette valeur sera affectée au modèle form de la page par exécution du code form.setInputText("jean"). On retiendra que dans le modèle M de la page P, les champs privés de M qui mémorisent la valeur d'un champ de saisie de P doivent avoir une méthode set,

  • une fois le modèle M de la page P mis à jour par les valeurs postées, l'événement qui a provoqué le POST de la page P peut être traité. C'est l'étape [E]. On notera que si le gestionnaire de cet événement appartient au bean M, il a accès aux valeurs du formulaire P qui ont été stockées dans les champs de ce même bean.
  • l'étape [E] va rendre au contrôleur JSF, une clé de navigation. Dans nos exemples, ce sera toujours le nom de la page XHTML à afficher, sans le suffixe .xhtml. C'est l'étape [F]. Une autre façon de faire est de renvoyer une clé de navigation qui sera recherchée dans le fichier [faces-config.xml]. Nous avons décrit ce cas.

Nous retiendrons de ce qui précède que :

  • une page P affiche les champs C de son modèle M avec des méthodes [M].getC(),
  • les champs C du modèle M d'une page P sont initialisés avec les valeurs saisies dans la page P à l'aide des méthodes [M].setC(saisie). Dans cette étape, peuvent intervenir des processus de conversion et de validation susceptibles d'échouer. Dans ce cas, l'événement qui a provoqué le POST de la page P n'est pas traité et la page est renvoyée de nouveau au client telle que celui-ci l'a saisie.

Le modèle [Form.java] de la page [index.xhtml] sera le suivant :

[Form.java]
CacherSélectionnez

Les champs des lignes 16-27 sont utilisés aux endroits suivants du formulaire :

Image non disponible

III-E-8. La page [index.xhtml]

La page [index.xhtml] qui génère la vue précédente est la suivante :

[index.xhtml]
CacherSélectionnez

Nous allons étudier successivement les principaux composants de cette page. On notera la structure générale d'un formulaire JSF :

 
CacherSélectionnez

Les composants d'un formulaire doivent être à l'intérieur d'une balise <h:form> (lignes 12-16). La balise <f:view> (lignes 7-18) est nécessaire si on internationalise l'application. Par ailleurs, un formulaire doit disposer d'un moyen d'être posté (POST), souvent un lien ou un bouton comme dans la ligne 14. Il peut être posté également par de nombreux événements (changement d'une sélection dans une liste, changement de champ actif, frappe d'un caractère dans un champ de saisie…).

III-E-9. Le style du formulaire

Afin de rendre plus lisibles les colonnes du tableau du formulaire, celui est accompagné d'une feuille de style :

 
CacherSélectionnez
  • ligne 4 : la feuille de style de la page est définie à l'intérieur de la balise HTML <head>, par la balise :
 
CacherSélectionnez

La feuille de style sera cherchée dans le dossier [resources] :

Image non disponible

Dans la balise :

 
CacherSélectionnez
  • library est le nom du dossier contenant la feuille de style,
  • name est le nom de la feuille de style.

Voyons une utilisation de cette feuille de style :

 
CacherSélectionnez

La balise <h:panelGrid columns="3"/> définit un tableau à trois colonnes. L'attribut columnClasses permet de donner un style à ces colonnes. Les valeurs col1,col2,col3 de l'attribut columnClasses désignent les styles respectifs des colonnes 1, 2 et 3 du tableau. Ces styles sont cherchés dans le feuille de style de la page :

 
CacherSélectionnez
  • lignes 7-9 : le style nommé col1,
  • lignes 11-13 : le style nommé col2,
  • lignes 15-17 : le style nommé col3,

Ces trois styles définissent la couleur de fond de chacune des colonnes.

  • lignes 19-23 : le style entete sert à définir le style des textes de la première ligne du tableau :
     
    CacherSélectionnez
  • lignes 1-5 : le style info sert à définir le style des textes de la première colonne du tableau :
 
CacherSélectionnez

Nous insisterons peu sur l'utilisation des feuilles de style car celles-ci méritent à elles seules un livre et que par ailleurs leur élaboration en est souvent confiée à des spécialistes. Néanmoins, nous avons souhaité en utiliser une, minimaliste, afin de rappeler que leur usage est indispensable.

Regardons maintenant comment a été définie l'image de fond de la page :

 
CacherSélectionnez

L'image de fond est fixée par l'attribut style de la balise <h:body>. Cet attribut permet de fixer des éléments de style. L'image de fond est dans le dossier [resources/images/standard.jpg] :

Image non disponible

Cette image est obtenue via l'URL [/mv-jsf2-03/resources/images/standard.jpg]. On pourrait donc écrire :

 
CacherSélectionnez

/mv-jsf2-03 est le contexte de l'application. Ce contexte est fixé par l'administrateur du serveur web et peut donc changer. Ce contexte peut être obtenu par l'expression EL ${request.contextPath}. Aussi préfèrera-t-on l'attribut style suivant :

 
CacherSélectionnez

qui sera valide quelque soit le contexte.

III-E-10. Les deux cycles demande client / réponse serveur d'un formulaire

Revenons sur ce qui a déjà été expliqué page dans un cas général et appliquons-le au formulaire étudié. Celui-ci sera testé dans l'environnement JSF classique :

Image non disponible

Ici, il n'y aura pas de gestionnaires d'événements ni de couche [métier]. Les étapes [2x] n'existeront donc pas. On distinguera le cas où le formulaire F est demandée initialement par le navigateur du cas où l'utilisateur ayant provoqué un événement dans le formulaire F, celui-ci est traité par le contrôleur [Faces Servlet]. Il y a deux cycles demande client / réponse serveur qui sont différents.

  • le premier correspondant à la demande initiale de la page est provoqué par une opération GET du navigateur sur l'URL du formulaire,
  • le second correspondant à la soumission des valeurs saisies dans la page est provoqué par une opération POST sur cette même URL.

Selon la nature de la demande GET ou POST du navigateur, le traitement de la requête par le contrôleur [Faces Servlet] diffère.

[cas 1 - demande initiale du formulaire F]

Le navigateur demande l'URL de la page avec un GET. Le contrôleur [Faces Servlet] va passer directement à l'étape [4] de rendu de la réponse. Le formulaire [index.xhtml] va être initialisé par son modèle [Form.java] et être envoyé au client qui reçoit la vue suivante :

Image non disponible

Les échanges HTTP client / serveur sont les suivants à cette occasion :

Demande HTTP du client :

 
CacherSélectionnez

Ligne 1, on voit le GET du navigateur.

Réponse HTTP du serveur :

 
CacherSélectionnez

Non montré ici, la ligne 7 est suivie d'une ligne vide et du code HTML du formulaire. C'est ce code que le navigateur interprète et affiche.

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

L'utilisateur remplit le formulaire et le valide par le bouton [Valider]. Le navigateur demande alors l'URL du formulaire avec un POST. Le contrôleur [Faces Servlet] traite cette requête, met à jour le modèle [Form.java] du formulaire [index.xhtml], et renvoie de nouveau le formulaire [index.xhtml] mis à jour par ce nouveau modèle. Examinons ce cycle sur un exemple :

Image non disponible

Ci-dessus, l'utilisateur a fait ses saisies et les valide. Il reçoit en réponse la vue suivante :

Image non disponible

Les échanges HTTP client / serveur sont les suivants à cette occasion :

Demande HTTP du client :

 
CacherSélectionnez

En ligne 1, le POST fait par le navigateur. En ligne 14, les valeurs saisies par l'utilisateur. On peut par exemple y découvrir le texte mis dans le champ de saisie :

 
CacherSélectionnez

Dans la ligne 14, le champ caché javax.faces.ViewState a été posté. Ce champ représente, sous forme codée, l'état du formulaire tel qu'il a été envoyé initialement au navigateur lors de son GET initial.

Réponse HTTP du serveur :

 
CacherSélectionnez

Non montré ici, la ligne 6 est suivie d'une ligne vide et du code HTML du formulaire mis à jour par son nouveau modèle issu du POST.

Nous examinons maintenant les différentes composantes de ce formulaire.

III-E-11. Balise <h:inputText>

La balise <h:inputText> génère une balise HTML <input type="text" …>.

Considérons le code suivant :

 
CacherSélectionnez

et son modèle [Form.java] :

[Form.java]
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • la balise <h:panelGroup> (lignes 3-6) permet de regrouper plusieurs éléments dans une même cellule du tableau généré par la balise <h:panelGrid> de la ligne 20 du code complet de la page, (cf page ). Le texte [2] est généré par la ligne 4. Le champ de saisie [3] est généré par la ligne [5]. Ici, la méthode getInputText de [Form.java] (lignes 3-5 du code Java) a été utilisée pour générer le texte du champ de saisie,
  • la ligne 7 du code XHTML génère [4]. C'est de nouveau la méthode getInputText de [Form.java] qui est utilisée pour générer le texte [4].

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez

Les balises HTML <tr> et <td> sont générées par la balise <h:panelGrid> utilisée pour générer le tableau du formulaire.

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3, 4] :

Image non disponible

La valeur du champ [1] est postée de la façon suivante :

 
CacherSélectionnez

En [2], le formulaire est validé avec le bouton suivant :

 
CacherSélectionnez

La balise <h:commandButton> n'a pas d'attribut action. Dans ce cas, aucun gestionnaire d'événement n'est invoqué ni aucune règle de navigation. Après traitement, la même page est renvoyée. Revoyons son cycle de traitement :

Image non disponible
  • en [A] la page P est restaurée telle qu'elle avait été envoyée. Cela signifie que le composant d'id inputText est restauré avec sa valeur initiale "texte",
  • en [B], les valeurs postées par le navigateur (saisies par l'utilisateur) sont affectées aux composants de la page P. Ici, le composant d'id inputText reçoit la valeur "un nouveau texte",
  • en [C], les conversions et validations ont lieu. Ici, il n'y en a aucune. Dans le modèle M, le champ associé au composant d'id inputText est le suivant :
 
CacherSélectionnez

Comme les valeurs saisies sont de type String, il n'y a pas de conversion à faire. Par ailleurs, aucune règle de validation n'a été créée. Nous en construirons ultérieurement.

  • en [D], les valeurs saisies sont affectées au modèle. Le champ inputText de [Form.java] reçoit la valeur "un nouveau texte",
  • en [E], rien n'est fait car aucun gestionnaire d'événement n'a été associé au bouton [Valider].
  • en [F], la page P est de nouveau envoyée au client car le bouton [Valider] n'a pas d'attribut action. Les lignes suivantes de [index.xhtml] sont alors exécutées :
 
CacherSélectionnez

Les lignes 5 et 7 utilisent la valeur du champ inputText du modèle qui est désormais "un nouveau texte". D'où l'affichage obtenu :

Image non disponible

III-E-12. Balise <h:inputSecret>

La balise <h:inputSecret> génère une balise HTML <input type="password" …>. C'est un champ de saisie analogue à celui de la balise JSF <h:inputText> si ce n'est que chaque caractère tapé par l'utilisateur est remplacé visuellement par un caractère *.

Considérons le code suivant :

 
CacherSélectionnez

et son modèle dans [Form.java] :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1]
  • le texte [2] est généré par la ligne 4. Le champ de saisie [3] est généré par la ligne [5]. Normalement, la méthode getInputSecret de [Form.java] aurait du être utilisée pour générer le texte du champ de saisie. Il y a une exception lorsque celui-ci est de type " mot de passe ". La balise <h:inputSecret> ne sert qu'à lire une saisie, pas à l'afficher.
  • la ligne 7 du code XHTML génère [4]. Ici la méthode getInputSecret de [Form.java] a été utilisée pour générer le texte [4] (cf ligne 1 du code Java).

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez
  • ligne 3 : la balise HTML <input type= "password " …/> générée par la balise JSF <h:inputSecret>

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3] :

Image non disponible

La valeur du champ [1] est postée de la façon suivante :

 
CacherSélectionnez

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ inputSecret de [Form.java] a reçu alors la valeur mdp. Parce que le formulaire [index.xhtml] n'a défini aucune règle de navigation, ni aucun gestionnaire d'événement, il est réaffiché après la mise à jour de son modèle. On retombe alors dans l'affichage fait à la demande initiale de la page [index.xhtml] où simplement le champ inputSecret du modèle a changé de valeur [3].

III-E-13. Balise <h:inputTextArea>

La balise <h:inputTextArea> génère une balise HTML <textarea …>texte</textarea>. C'est un champ de saisie analogue à celui de la balise JSF <h:inputText> si ce n'est qu'ici, on peut taper plusieurs lignes de texte.

Considérons le code suivant :

 
CacherSélectionnez

et son modèle dans [Form.java] :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • le texte [2] est généré par la ligne 4. Le champ de saisie [3] est généré par la ligne [5]. Son contenu a été généré par appel à la méthode getInputTextArea du modèle, qui a rendu la valeur définie en ligne 1 du code Java ci-dessus,
  • la ligne 7 du code XHTML génère [4]. Ici la méthode getInputTextArea de [Form.java] a été de nouveau utilisée. La chaîne " ligne1\nligne2 " contenait des sauts de ligne \n. Ils y sont toujours. Mais insérés dans un flux HTML, ils sont affichés comme des espaces par les navigateurs. La balise HTML <textarea> qui affiche [3], elle, interprète correctement les sauts de ligne.

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez
  • lignes 3-5 : la balise HTML <textarea>…</textarea> générée par la balise JSF <h:inputTextArea>

Maintenant, ci-dessous, saisissons une valeur dans le champ de saisie [1] et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3] :

Image non disponible

La valeur du champ [1] postée est la suivante :

 
CacherSélectionnez

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ textArea de [Form.java] a reçu alors la valeur " Tutoriel JSF\npartie1 ". Le réaffichage de [index.xhtml] montre que champ textArea du modèle a bien été mis à jour [3].

III-E-14. Balise <h:selectOneListBox>

La balise <h:selectOneListBox> génère une balise HTML <select>…</select>. Visuellement, elle génère une liste déroulante ou une liste avec ascenseur.

Considérons le code suivant :

 
CacherSélectionnez

et son modèle dans [Form.java] :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1]
  • le texte [2] est généré par la ligne 4. La liste déroulante [3] est générée par les lignes [5-9]. C'est la valeur de l'attribut size= "1" qui fait que la liste n'affiche qu'un élément. Si cet attribut est manquant, la valeur par défaut de l'attribut size est 1. Les éléments de la liste ont été générés par les balises <f:selectItem> des lignes 6-8. Ces balises ont la syntaxe suivante :
 
CacherSélectionnez

La valeur de l'attribut itemLabel est ce qui est affiché dans la liste. La valeur de l'attribut itemValue est la valeur de l'élément. C'est cette valeur qui sera envoyée au contrôleur [Faces Servlet] si l'élément est sélectionné dans la liste déroulante.

L'élément affiché en [3] a été déterminé par appel à la méthode getSelectOneListBox1() (ligne 5). Le résultat "2" obtenu (ligne 1 du code Java) a fait que l'élément de la ligne 7 de la liste déroulante a été affiché, ceci parce que son attribut itemValue vaut "2",

  • la ligne 11 du code XHTML génère [4]. Ici la méthode getSelectOneListBox1 de [Form.java] a été de nouveau utilisée.

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez
  • lignes 3 et 7 : la balise HTML <select …>…</select> générée par la balise JSF <h:selectOneListBox>,
  • lignes 4-6 : les balises HTML <option …> … </option> générées par les balises JSF <f:selectItem>,
  • ligne 5 : le fait que l'élément de valeur="2" soit sélectionné dans la liste se traduit par la présence de l'attribut selected="selected".

Maintenant, ci-dessous, choisissons [1] une nouvelle valeur dans la liste et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3] :

Image non disponible

La valeur du champ [1] postée est la suivante :

 
CacherSélectionnez

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. L'élément HTML

 
CacherSélectionnez

a été sélectionné. Le navigateur a envoyé la chaîne "3" comme valeur du composant JSF ayant produit la liste déroulante :

 
CacherSélectionnez

Le contrôleur JSF va utiliser la méthode setSelectOneListBox1("3") pour mettre à jour le modèle de la liste déroulante. Aussi après cette mise à jour, le champ du modèle [Form.java]

 
CacherSélectionnez

contient désormais la valeur "3".

Lorsque la page [index.xhtml] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :

  • elle détermine l'élément de la liste déroulante qui doit être affiché [3],
  • la valeur du champ selectOneListBox1 est affichée en [4].

Considérons une variante de la balise <h:selectOneListBox> :

 
CacherSélectionnez

Le modèle dans [Form.java] de la balise <h:selectOneListBox> de la ligne 5 est le suivant :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • le texte [2] est généré par la ligne 4. La liste avec ascenseur [3] est générée par les lignes [5-11]. C'est la valeur de l'attribut size= "3 " qui fait qu'on a une liste avec ascenseur plutôt qu'une liste déroulante. Les éléments de la liste ont été générés par les balises <f:selectItem> des lignes 6-8,

L'élément sélectionné en [3] a été déterminé par appel à la méthode getSelectOneListBox2() (ligne 5). Le résultat "3" obtenu (ligne 1 du code Java) a fait que l'élément de la ligne 8 de la liste a été affiché, ceci parce que son attribut itemValue vaut "3",

  • la ligne 13 du code XHTML génère [4]. Ici la méthode getSelectOneListBox2 de [Form.java] a été de nouveau utilisée.

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez
  • ligne 6 : le fait que l'élément de valeur="3" soit sélectionné dans la liste se traduit par la présence de l'attribut selected="selected".

Maintenant, ci-dessous, choisissons [1] une nouvelle valeur dans la liste et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3] :

Image non disponible

La valeur postée pour le champ [1] est la suivante :

 
CacherSélectionnez

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. L'élément HTML

 
CacherSélectionnez

a été sélectionné. Le navigateur a envoyé la chaîne "5" comme valeur du composant JSF ayant produit la liste déroulante :

 
CacherSélectionnez

Le contrôleur JSF va utiliser la méthode setSelectOneListBox2("5") pour mettre à jour le modèle de la liste. Aussi après cette mise à jour, le champ

 
CacherSélectionnez

contient-il désormais la valeur "5".

Lorsque la page [index.xhtml] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :

  • elle détermine l'élément de la liste qui doit être sélectionné [3],
  • la valeur du champ selectOneListBox2 est affiché en [4].

III-E-15. Balise <h:selectManyListBox>

La balise <h:selectmanyListBox> génère une balise HTML <select multiple= "multiple ">…</select> qui permet à l'utilisateur de sélectionner plusieurs éléments dans une liste.

Considérons le code suivant :

 
CacherSélectionnez

et son modèle dans [Form.java] :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1]
  • le texte [2] est généré par la ligne 4. La liste [3] est générée par les lignes [5-11]. L'attribut size="3" fait que la liste affiche à un moment donné trois de ces éléments. Les éléments sélectionnés dans la liste ont été déterminés par appel à la méthode getSelectManyListBox() (ligne 5) du modèle Java. Le résultat {"1","3"} obtenu (ligne 1 du code Java) est un tableau d'éléments de type String. Chacun de ces éléments sert à sélectionner l'un des éléments de la liste. Ici, les éléments des lignes 6 et 10 ayant leur attribut itemValue dans le tableau {"1","3"} seront sélectionnés. C'est ce que montre [3].
  • la ligne 14 du code XHTML génère [4]. Il est ici fait appel, non pas à la méthode getSelectManyListBox du modèle Java de la liste mais à la méthode getSelectManyListBoxValue suivante :
 
CacherSélectionnez

Si on avait fait appel à la méthode getSelectManyListBox, on aurait obtenu un tableau de String. Pour inclure cet élément dans le flux HTML, le contrôleur aurait appel à sa méthode toString. Or celle-ci pour un tableau ne fait que rendre le " hashcode " de celui-ci et non pas la liste de ses éléments comme nous le souhaitons. Aussi utilise-t-on la méthode getSelectManyListBoxValue ci-dessus pour obtenir une chaîne de caractères représentant le contenu du tableau,

  • la ligne 12 du code XHTML génère le bouton [5]. Lorsque ce bouton est cliqué, le code Javascript de l'attribut onclick est exécuté. Il sera embarqué au sein de la page HTML qui va être générée par le code JSF. Pour le comprendre, nous avons besoin de connaître la nature exacte de celle-ci.

Le flux HTML généré par la page XHTML est le suivant :

 
CacherSélectionnez
  • lignes 3 et 9 : la balise HTML <select multiple= "multiple "…>…</select> générée par la balise JSF <h:selectManyListBox>. C'est la présence de l'attribut multiple qui indique qu'on a affaire à une liste à sélection multiple,
  • le fait que le modèle de la liste soit le tableau de String {"1","3"} fait que les éléments de la liste des lignes 4 (value="1") et 6 (value="3") ont l'attribut selected="selected",
  • ligne 10 : lorsqu'on clique sur le bouton [Raz], le code Javascript de l'attribut onclick s'exécute. La page est représentée dans le navigateur par un arbre d'objets souvent appelé DOM (Document Object Model). Chaque objet de l'arbre est accessible au code Javascript via son attribut name. La liste de la ligne 3 du code HTML ci-dessus s'appelle formulaire:selectManyListBox. Le formulaire lui-même peut être désigné de diverses façons. Ici, il est désigné par la notation this.formthis désigne le bouton [Raz] et this.form le formulaire dans lequel se trouve ce bouton. La liste formulaire:selectManyListBox se trouve dans ce même formulaire. Aussi la notation this.form['formulaire:selectManyListBox'] désigne-t-elle l'emplacement de la liste dans l'arbre des composants du formulaire. L'objet représentant une liste a un attribut selectedIndex qui a pour valeur le n° de l'élément sélectionné dans la liste. Ce n° commence à 0 pour désigner le premier élément de la liste. La valeur -1 indique qu'aucun élément n'est sélectionné dans la liste. Le code Javascript affectant la valeur -1 à l'attribut selectedIndex a pour effet de désélectionner tous les éléments de la liste s'il y en avait.

Maintenant, ci-dessous, choisissons [1] de nouvelles valeurs dans la liste (pour sélectionner plusieurs éléments dans la liste, maintenir la touche Ctrl appuyée en cliquant) et validons le formulaire avec le bouton [Valider] [2]. Nous obtenons en réponse la page [3,4] :

Image non disponible

La valeur du champ [1] postée est la suivante :

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

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Les éléments HTML

 
CacherSélectionnez

ont été sélectionnés. Le navigateur a envoyé les trois chaînes "3", "4", "5" comme valeurs du composant JSF ayant produit la liste déroulante :

 
CacherSélectionnez

La méthode setSelectManyListBox du modèle va être utilisée pour mettre à ce jour ce modèle avec les valeurs envoyées par le navigateur :

 
CacherSélectionnez

Ligne 3, on voit que le paramètre de la méthode est un tableau de String. Ici ce sera le tableau {"3","4","5"}. Après cette mise à jour, le champ

 
CacherSélectionnez

contient désormais le tableau {"3","4","5"}.

Lorsque la page [index.xhtml] est réaffichée à l'issue de son traitement, cette valeur provoque l'affichage [3,4] ci-dessus :

  • elle détermine les éléments de la liste qui doivent être sélectionnés [3],
  • la valeur du champ selectManyListBox est affichée en [4].

III-E-16. Balise <h:selectOneMenu>

La balise <h:selectOneMenu> est identique à la balise <h:selectOneListBox size="1">. Dans l'exemple, le code JSF exécuté est le suivant :

 
CacherSélectionnez

Le modèle de la balise <h:selectOneMenu> dans [Form.java] est le suivant :

 
CacherSélectionnez

A la demande initiale de la page [index.xhtml], le code précédent génère la vue :

Image non disponible

Un exemple d'exécution pourrait être celui qui suit :

Image non disponible

La valeur postée pour le champ [1] est la suivante :

 
CacherSélectionnez

III-E-17. Balise <h:selectManyMenu>

La balise <h:selectManyMenu> est identique à la balise <h:selectManyListBox size="1">. Le code JSF exécuté dans l'exemple est le suivant :

 
CacherSélectionnez

Le modèle de la balise <h:selectManyMenu> dans [Form.java] est le suivant :

 
CacherSélectionnez

A la demande initiale de la page [index.xhtml], le code précédent génère la page :

Image non disponible

La liste [1] contient les textes "un"…, "cinq" avec les éléments "un" et "deux" sélectionnés. Le code HTML généré est le suivant :

 
CacherSélectionnez

On voit ci-dessus, en lignes 4 et 5 que les éléments "un" et "deux" sont sélectionnés (présence de l'attribut selected).

Il est difficile de donner une copie d'écran d'un exemple d'exécution car on ne peut pas montrer les éléments sélectionnés dans le menu. Le lecteur est invité à faire le test lui-même (pour sélectionner plusieurs éléments dans la liste, maintenir la touche Ctrl appuyée en cliquant).

III-E-18. Balise <h:inputHidden>

La balise <h:inputHidden> n'a pas de représentation visuelle. Elle ne sert qu'à insérer une balise HTML <input type="hidden" value="…"/> dans le flux HTML de la page. Inclus à l'intérieur d'une balise <h:form>, leurs valeurs font partie des valeurs envoyées au serveur lorsque le formulaire est posté. Parce que ce sont des champs de formulaire et que l'utilisateur ne les voie pas, on les appelle des champs cachés. L'intérêt de ces champs est de garder de la mémoire entre les différents cycles demande / réponse d'un même client :

  • le client demande un formulaire F. Le serveur le lui envoie et met une information I dans un champ caché C, sous la forme <h:inputHidden id="C" value="I"/>,
  • lorsque le client a rempli le formulaire F et qu'il le poste au serveur, la valeur I du champ C est renvoyée au serveur. Celui-ci peut alors retrouver l'information I qu'il avait stockée dans la page. On a ainsi créé une mémoire entre les deux cycles demande / réponse,
  • JSF utilise lui-même cette technique. L'information I qu'il stocke dans le formulaire F est la valeur de tous les composants de celui-ci. Il utilise le champ caché suivant pour cela :
 
CacherSélectionnez

Le champ caché s'appelle javax.faces.ViewState et sa valeur est une chaîne qui représente sous forme codée la valeur de tous les composants de la page envoyée au client. Lorsque celui-ci renvoie la page après avoir fait des saisies dans le formulaire, le champ caché javax.faces.ViewState est renvoyé avec les valeurs saisies. C'est ce qui permet au contrôleur JSF de reconstituer la page telle qu'elle avait été envoyée initialement. Ce mécanisme a été expliqué page .

Le code JSF de l'exemple est le suivant :

 
CacherSélectionnez

Le modèle de la balise <h:inputHidden> dans [Form.java] est le suivant :

 
CacherSélectionnez

Ce qui donne l'affichage suivant lors de la demande initiale de la page [index.xhtml] :

Image non disponible
  • la ligne 2 génère [1], la ligne 4 [2]. La ligne 3 ne génère aucun élément visuel.

Le code HTML généré est le suivant :

 
CacherSélectionnez

Au moment du POST du formulaire, la valeur "initial" du champ nommé formulaire:inputHidden de la ligne 3 sera postée avec les autres valeurs du formulaire. Le champ

 
CacherSélectionnez

sera mis à jour avec cette valeur, qui est celle qu'il avait déjà initialement. Cette valeur sera intégrée dans la nouvelle page renvoyée au client. On obtient donc toujours la copie d'écran ci-dessus.

La valeur postée pour le champ caché est la suivante :

 
CacherSélectionnez

III-E-19. Balise <h:selectBooleanCheckBox>

La balise <h:selectBooleanCheckBox> génère une balise HTML <input type="checkbox" …>.

Considérons le code JSF suivant :

 
CacherSélectionnez

Le modèle de la balise <h:selectBooleanCheckbox> de ligne 5 ci-dessus dans [Form.java] est le suivant:

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • le texte [2] est généré par la ligne 4. La case à cocher [3] est générée par la ligne [5]. Ici, la méthode getSelectBooleanCheckbox de [Form.java] a été utilisée pour cocher ou non la case. La méthode rendant le booléen true (cf code Java), la case a été cochée,
  • la ligne 7 du code XHTML génère [4]. C'est de nouveau la méthode getSelectBooleanCheckbox de [Form.java] qui est utilisée pour générer le texte [4].

Le flux HTML généré par le code JSF précédent est le suivant :

 
CacherSélectionnez

En [4], on voit la balise HTML <input type="checkbox"> qui a été générée. La valeur true du modèle associé a fait que l'attribut checked="checked" a été ajouté à la balise. Ce qui fait que la case est cochée.

Maintenant, ci-dessous, décochons la case [1], validons le formulaire [2] et regardons le résultat obtenu [3, 4] :

Image non disponible

Parce que la case est décochée, il n'y a pas de valeur postée pour le champ [1].

La validation du formulaire par [2] a provoqué la mise à jour du modèle [Form.java] par la saisie [1]. Le champ selectBooleanCheckbox de [Form.java] a reçu alors la valeur false. Le réaffichage de [index.xhtml] montre que le champ selectBooleanCheckbox du modèle a bien été mis à jour [3] et [4]. Il est intéressant de noter ici que c'est grâce au champ caché javax.faces.ViewState que JSF a été capable de dire que la case à cocher initialement cochée avait été décochée par l'utilisateur. En effet, la valeur d'une case décochée ne fait pas partie des valeurs postées par le navigateur. Grâce à l'arbre des composants stocké dans le champ caché javax.faces.ViewState JSF retrouve le fait qu'il y avait une case à cocher nommée "selectBooleanCheckbox" dans le formulaire et que sa valeur ne fait pas partie des valeurs postées par le navigateur client. Il peut en conclure qu'elle était décochée dans le formulaire posté, ce qui lui permet d'affecter le booléen false au modèle Java associé :

 
CacherSélectionnez

III-E-20. Balise <h:selectManyCheckBox>

La balise <h:selectManyCheckBox> génère un groupe de cases à cocher et donc plusieurs balises HTML <input type="checkbox" …>. Cette balise est le pendant de la balise <h:selectManyListBox>, si ce n'est que les éléments à sélectionner sont présentés sous forme de cases à cocher contigües plutôt que sous forme de liste. Ce qui a été dit pour la balise <h:selectManyListBox> reste valide ici.

Considérons le code JSF suivant :

 
CacherSélectionnez

Le modèle de la balise <h:selectManyCheckbox> de ligne 5 ci-dessus dans [Form.java] est le suivant:

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la page obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • le texte [2] est généré par la ligne 4. Les cases à cocher [3] sont générées par les lignes 5-10. Pour chacune d'elles :
  • l'attribut itemLabel définit le texte affiché près de la case à cocher ;
  • l'attribut itemvalue définit la valeur qui sera postée au serveur si la case est cochée,

Le modèle des quatre cases est le champ Java suivant :

 
CacherSélectionnez

Ce tableau définit :

  • lorsque la page est affichée, les cases qui doivent être cochées. Ceci est fait via leur valeur, c.a.d. leur champ itemValue. Ci-dessus, les cases ayant leurs valeurs dans le tableau {"1","3"} seront cochées. C'est ce qui est vu sur la copie d'écran ci-dessus ;
  • lorsque la page est postée, le modèle selectManyCheckbox reçoit le tableau des valeurs des cases que l'utilisateur a cochées. C'est ce que nous allons voir prochainement,
 
CacherSélectionnez

Le flux HTML généré par le code JSF précédent est le suivant :

 
CacherSélectionnez

Quatre balises HTML <input type="checkbox" …> ont été générées. Les balises des lignes 3 et 7 ont l'attribut checked="checked" qui font qu'elles apparaissent cochées. On notera qu'elles ont toutes le même attribut name="formulaire:selectManyCheckbox", autrement dit les quatre champs HTML ont le même nom. Si les cases des lignes 5 et 9 sont cochées par l'utilisateur, le navigateur enverra les valeurs des quatre cases à cocher sous la forme :

 
CacherSélectionnez

et le modèle des quatre cases

 
CacherSélectionnez

recevra le tableau {"2","4"}.

Vérifions-le ci-dessous. En [1], on fait le changement, en [2] on valide le formulaire. En [3] le résultat obtenu :

Image non disponible

Les valeurs postées pour les champs [1] sont les suivantes :

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

III-E-21. Balise <h:selectOneRadio>

La balise <h:selectOneRadio> génère un groupe de boutons radio exclusifs les uns des autres.

Considérons le code JSF suivant :

 
CacherSélectionnez

Le modèle de la balise <h:selectOneRadio> de la ligne 5 ci-dessus est le suivant dans [Form.java] :

 
CacherSélectionnez

Lorsque la page [index.xhtml] est demandée la première fois, la vue obtenue est la suivante :

Image non disponible
  • la ligne 2 du code XHTML génère [1],
  • le texte [2] est généré par la ligne 4. Les boutons radio [3] sont générés par les lignes 5-10. Pour chacun d'eux :
  • l'attribut itemLabel définit le texte affiché près du bouton radio ;
  • l'attribut itemvalue définit la valeur qui sera postée au serveur si le bouton est coché,

Le modèle des quatre boutons radio est le champ Java suivant :

 
CacherSélectionnez

Ce modèle définit :

  • lorsque la page est affichée, l'unique bouton radio qui doit être coché. Ceci est fait via leur valeur, c.a.d. leur champ itemValue. Ci-dessus, le bouton radio ayant la valeur " 2 " sera cochée. C'est ce qui est vu sur la copie d'écran ci-dessus ;
  • lorsque la page est postée, le modèle selectOneRadio reçoit la valeur du bouton radio qui a été coché. C'est ce que nous allons voir prochainement,

Le flux HTML généré par le code JSF précédent est le suivant :

 
CacherSélectionnez

Quatre balises HTML <input type="radio" …> ont été générées. La balise de la ligne 8 a l'attribut checked="checked" qui fait que le bouton radio correspondant apparaît coché. On notera que les balises ont toutes le même attribut name="formulaire:selectOneRadio", autrement dit les quatre champs HTML ont le même nom. C'est la condition pour avoir un groupe de boutons radio exclusifs : lorsque l'un est coché, les autres ne le sont pas.

Ci-dessous, en [1], on coche un des boutons radio, en [2] on valide le formulaire, en [3] le résultat obtenu :

Image non disponible

La valeur postée pour le champ [1] est la suivante :

 
CacherSélectionnez

III-F. Exemple mv-jsf2-04 : listes dynamiques

III-F-1. L'application

L'application est la même que précédemment :

Image non disponible

Les seuls changements proviennent de la façon dont sont générés les éléments des listes des zones [1] et [2]. Ils sont ici générés dynamiquement par du code Java alors que dans la version précédente ils étaient écrits "en dur" dans le code de la page JSF.

III-F-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

Le projet [mv-jsf2-04] est identique au projet [mv-jsf2-03] aux différences près suivantes :

  • en [1], dans la page JSF, les éléments des listes ne vont plus être écrites "en dur" dans le code,
  • en [2], le modèle de la page JSF [1] va être modifié,
  • en [3], l'un des messages va être modifié.

III-F-3. La page [index.xhtml] et son modèle [Form.java]

La page JSF [index.xhtml] devient la suivante :

[index.xhtml]
CacherSélectionnez

Les modifications apportées sont illustrées par les lignes 26-28. Là où auparavant, on avait le code :

 
CacherSélectionnez

on a désormais celui-ci :

 
CacherSélectionnez

Les trois balises <f:selectItem> des lignes 2-4 ont été remplacées par l'unique balise <f:selectItems> de la ligne b. Cette balise a un attribut value dont la valeur est une collection d'éléments de type javax.faces.model.SelectItem. Ci-dessus, la valeur de l'attribut value va être obtenue par appel de la méthode [form].getSelectOneListbox1Items suivante :

 
CacherSélectionnez
  • ligne 1, la méthode getSelectOneListbox1Items rend un tableau d'éléments de type javax.faces.model.SelectItem construit par la méthode privée getItems de la ligne 5. On notera que la méthode getSelectOneListbox1Items n'est pas le getter d'un champ privé selectOneListBox1Items,
  • la classe javax.faces.model.SelectItem a divers constructeurs.
Image non disponible

Nous utilisons ligne 8 de la méthode getItems, le constructeur SelectItem(Object value, String label) qui correspond à la balise JSF

 
CacherSélectionnez
  • lignes 5-10 : la méthode getItems(String label, int qte) construit un tableau de qte éléments de type SelectItem, où l'élément i est obtenu par le constructeur SelectItem(i, label+i).

Le code JSF

 
CacherSélectionnez

devient alors fonctionnellement équivalent au code JSF suivant :

 
CacherSélectionnez

Il est fait de même pour toutes les autres listes de la page JSF. On trouve ainsi dans le modèle [Form.java] les nouvelles méthodes suivantes :

 
CacherSélectionnez

III-F-4. Le fichier des messages

Un seul message est modifié :

 
CacherSélectionnez

III-F-5. Tests

Le lecteur est invité à tester cette nouvelle version.

Le plus souvent les éléments dynamiques d'un formulaire sont les résultats d'un traitement métier ou proviennent d'une base de données :

Image non disponible

Etudions la demande initiale de la page JSF [index.xhtml] par un GET du navigateur :

  • la page JSF est demandée [1],
  • le contrôleur [Faces Servlet] demande son affichage en [3]. Le moteur JSF qui traite la page fait appel au modèle [Form.java] de celle-ci, par exemple à la méthode getSelectOneListBox1Items. Cette méthode pourrait très bien rendre un tableau d'éléments de type SelectItem, à partir d'informations enregistrées dans une base de données. Pour cela, elle ferait appel à la couche [métier] [2b].

III-G. Exemple mv-jsf2-05 : navigation - session - gestion des exceptions

III-G-1. L'application

L'application est la même que précédemment si ce n'est que le formulaire se présente désormais sous la forme d'un assistant à plusieurs pages :

Image non disponible
  • en [1], la page 1 du formulaire - peut être obtenue également par le lien 1 de [2]
  • en [2], un groupe de 5 liens.
  • en [3], la page 2 du formulaire obtenue par le lien 2 de [2]
    Image non disponible
  • en [4], la page 3 du formulaire obtenue par le lien 3 de [2]
    Image non disponible
  • en [5], la page obtenue par le lien Lancer une exception de [2]
    Image non disponible
  • en [6], la page obtenue par le lien 4 de [2]. Elle récapitule les saisies faites dans les pages 1 à 3.

III-G-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

Le projet [mv-jsf2-05] introduit deux nouveautés :

  1. en [1], la page JSF [index.xhtml] est scindée en trois pages [form1.xhtml, form2.xhtml, form3.xhtml] sur lesquelles ont été réparties les saisies. La page [form4.xhtml] est une copie de la page [index.xhtml] du projet précédent. En [2], la classe [Form.java] reste inchangée. Elle va servir de modèle aux quatre pages JSF précédentes,
  2. en [3], une page [exception.xhtml] est ajoutée : elle sera utilisée lorsque se produira une exception dans l'application.

III-G-3. Les pages [form.xhtml] et leur modèle [Form.java]

III-G-3-a. Le code des pages XHTML

La page JSF [form1.xhtml] est la suivante :

[form1.xhtml]
CacherSélectionnez

et correspond à l'affichage suivant :

Image non disponible

On notera les points suivants :

  • ligne 16, le tableau qui avait auparavant trois colonnes, n'en a plus que deux. La colonne 3 qui affichait les valeurs du modèle a été supprimée. C'est [form4.xhtml] qui les affichera,
  • lignes 40-46 : un tableau de six liens. Les liens des lignes 44 et 46 ont une navigation statique : leur attribut action est codé en dur. Les autres liens ont une navigation dynamique : leur attribut action pointe sur une méthode du bean form chargée de rendre la clé de navigation. Les méthodes référencées dans [Form.java] sont les suivantes :
 
CacherSélectionnez

Nous ignorerons pour l'instant la méthode throwException de la ligne 17. Nous y reviendrons ultérieurement. Les méthodes doAction2 et doAction4 se contentent de rendre la clé de navigation sans faire de traitement. On aurait donc tout ausi bien pu écrire :

 
CacherSélectionnez

La méthode doAlea génère, elle, une clé de navigation aléatoire qui prend sa valeur dans l'ensemble {"form1","form2","form3"}.

Le code des pages [form2.xhtml, form3.xhtml, form3.xhtml] est analogue à celle de la page [form1.xhtml].

III-G-3-b. Durée de vie du modèle [Form.java] des pages [form*.xhtml]

Considérons la séquence d'actions suivantes :

Image non disponible
  • en [1], on remplit la page 1 et on passe à la page 3,
  • en [2], on remplit la page 3 et on revient à la page 1,
    Image non disponible
  • en [3], on retrouve la page 1 telle qu'on l'a saisie. On revient alors à la page 3,
  • en [4], la page 3 est retrouvée telle qu'elle a été saisie.

Le mécanisme du champ caché [javax.faces.ViewState] ne suffit pas à expliquer ce phénomène.

Lors du passage de [1] à [2], plusieurs étapes ont lieu :

  • le modèle [Form.java] est mis à jour avec le POST de [form1.jsp]. Notamment, le champ inputText reçoit la valeur "un autre texte",
  • la clé de navigation "form3" provoque l'affichage de [form3.xhtml]. Le ViewState embarqué dans [form3.xhtml] est l'état des seuls composants de [form3.xhtml] pas ceux de [form1.xhtml].

Lors du passage de [2] à [3] :

  • le modèle [Form.java] est mis à jour avec le POST de [form3.xhtml]. Si la durée de vie du modèle [Form.java] est request, un objet [Form.java] tout neuf est créé avant d'être mis à jour par le POST de [form3.xhtml]. Dans ce cas, le champ inputText du modèle retrouve sa valeur par défaut :
 
CacherSélectionnez

et la garde : en effet dans le POST de [form3.xhtml], rien ne vient mettre à jour le champ inputText qui fait partie du modèle de [form1.xhtml] et non de celui de [form3.xhtml],

  • la clé de navigation "form1" provoque l'affichage de [form1.xhtml]. La page affiche son modèle. Dans notre cas, le champ de saisie login lié au modèle inputText affichera texte et non pas la valeur "un autre texte" saisie en [1]. Pour que le champ inputText garde la valeur saisie en [1], la durée de vie du modèle [Form.java] doit être session et non request. Dans ce cas,
  • à l'issue du POST de [form1.xhtml], le modèle sera placé dans la session du client. Le champ inputText aura la valeur "un autre texte",
  • au moment du POST de [form3.xhtml], le modèle sera recherché dans cette session et mis à jour par le POST de [form3.xhtml]. Le champ inputText ne sera pas mis à jour par ce POST mais gardera la valeur "un autre texte" acquise à l'issue du POST de [form1.xhtml] [1].

La déclaration du bean [Form.java] est donc la suivante :

 
CacherSélectionnez

La ligne 8 donne au bean une portée session.

III-G-4. Gestion des exceptions

Revenons sur l'architecture générale d'une application JSF :

Image non disponible

Que se passe-t-il lorsque un gestionnaire d'événement ou bien un modèle récupère une exception provenant de la couche métier, une déconnexion imprévue d'avec une base de données par exemple ?

  • les gestionnaires d'événements [2a] peuvent intercepter toute exception remontant de la couche [métier] et rendre au contrôleur [Faces Servlet] une clé de navigation vers une page d'erreur spécifique à l'exception,
  • pour les modèles, cette solution n'est pas utilisable car lorsqu'ils sont sollicités [3,4], on est dans la phase de rendu d'une page XHTML précise et plus dans la phase de choix de celle-ci. Comment faire pour changer de page alors même qu'on est dans la phase de rendu de l'une d'elles ? Une solution simple mais qui ne convient pas toujours est de ne pas gérer l'exception qui va alors remonter jusqu'au conteneur de servlets qui exécute l'application. Celle-ci peut être configurée pour afficher une page particulière lorsqu'une exception remonte jusqu'au conteneur de servlets. Cette solution est toujours utilisable et nous l'examinons maintenant.

III-G-4-a. Configuration de l'application web pour la gestion des exceptions

La configuration d'une application web pour la gestion des exceptions se fait dans son fichier [web.xml] :

[web.xml]
CacherSélectionnez

Lignes 32-39, on trouve la définition de deux pages d'erreur. On peut avoir autant de balises <error-page> que nécessaires. La balise <location> indique la page à afficher en cas d'erreur. Le type de l'erreur associée à la page peut être défini de deux façons :

  • par la balise <exception-type> qui définit le type Java de l'exception gérée. Ainsi la balise <error-page> des lignes 36-39 indique que si le conteneur de servlets récupère une exception de type [java.lang.Exception] ou dérivé (ligne 37) au cours de l'exécution de l'application, alors il doit faire afficher la page [/faces/exception.xhtml] (ligne 38). En prenant ici, le type d'exception le plus générique [java.lang.Exception], on s'assure de gérer toutes les exceptions,
  • par la balise <error-code> (ligne 33) qui définit un code HTTP d'erreur. Par exemple, si un navigateur demande l'URL [http://machine:port/contexte/P] et que la page P n'existe pas dans l'application contexte, celle-ci n'intervient pas dans la réponse. C'est le conteneur de servlets qui génère cette réponse en envoyant une page d'erreur par défaut. La première ligne du flux HTTP de sa réponse contient un code d'erreur 404 indiquant que la page P demandée n'existe pas. On peut vouloir générer une réponse qui par exemple respecte la charte graphique de l'application ou qui donne des liens pour résoudre le problème. Dans ce cas, on utilisera une balise <error-page> avec une balise <error-code>404</error-code>.

Ci-dessus, le code HTTP d'erreur 500 est le code renvoyé en cas de " plantage " de l'application. C'est le code qui serait renvoyé si une exception remontait jusqu'au conteneur de servlets. Les deux balises <error-page> des lignes 28-35 sont donc probablement redondantes. On les a mises toutes les deux pour illustrer les deux façons de gérer une erreur.

III-G-4-b. La simulation de l'exception

Une exception est produite artificiellement par le lien [Lancer une exception] :

Image non disponible
Image non disponible

Un clic sur le lien [Lancer une exception] [1] provoque l'affichage de la page [2].

Dans le code des pages [formx.xhtml], le lien [Lancer une exception] est généré de la façon suivante :

 
CacherSélectionnez

Ligne 5, on voit que lors d'un clic sur le lien, la méthode [form].throwException va être exécutée. Celle-ci est la suivante :

 
CacherSélectionnez

On y lance une exception de type [java.lang.Exception]. Elle va remonter jusqu'au conteneur de servlets qui va alors afficher la page [/faces/exception.xhtml].

III-G-4-c. Les informations liées à une exception

Lorsqu'une exception remonte jusqu'au conteneur de servlets, celui-ci va faire afficher la page d'erreur correspondante en transmettant à celle-ci des informations sur l'exception. Celles-ci sont placées comme nouveaux attributs de la requête en cours de traitement. La requête d'un navigateur et la réponse qu'il va recevoir sont encapsulées dans des objets Java de type [HttpServletRequest request] et [HttpServletResponse response]. Ces objets sont disponibles à tous les étapes de traitement de la requête du navigateur.

Image non disponible

A réception de la requête HTTP du navigateur, le conteneur de servlets encapsule celle-ci dans l'objet Java [HttpServletRequest request] et crée l'objet [HttpServletResponse response] qui va permettre de générer la réponse. Dans cet objet, on trouve notamment le canal TCP-IP à utiliser pour le flux HTTP de la réponse. Tous les couches t1, t2…, tn qui vont intervenir dans le traitement de l'objet request, ont accès à ces deux objets. Chacune d'elles peut avoir accès aux éléments de la requête initiale request, et préparer la réponse en enrichissant l'objet response. Une couche de localisation pourra par exemple fixer la localisation de la réponse par la méthode response.setLocale(Locale l).

Les différentes couches ti peuvent se passer des informations via l'objet request. Celui-ci a un dictionnaire d'attributs, vide à sa création, qui peut être enrichi par les couches de traitement successives. Celles-ci peuvent mettre dans les attributs de l'objet request des informations nécessaires à la couche de traitement suivante. Il existe deux méthodes pour gérer les attributs de l'objet request :

  • void setAttribute(String s, Object o) qui permet d'ajouter aux attributs un objet o identifié par la chaîne s,
  • Object getAttribute(String s) qui permet d'obtenir l'attribut o identifié par la chaîne s.

Lorsqu'une exception remonte jusqu'au conteneur de servlets, ce dernier met les attributs suivants dans la requête en cours de traitement :

clé valeur
javax.servlet.error.status_code le code d'erreur HTTP qui va être renvoyé au client
javax.servlet.error.exception le type Java de l'exception accompagné du message d'erreur.
javax.servlet.error.request_uri l'URL demandée lorsque l'exception s'est produite
javax.servlet.error.servlet_name la servlet qui traitait la requête lorsque l'exception s'est produite

Nous utiliserons ces attributs de la requête dans la page [exception.xhtml] pour les afficher.

III-G-4-d. La page d'erreur [exception.xhtml]

Son contenu est le suivant :

[exception.xhtml]
CacherSélectionnez
III-G-4-d-i. Les expressions de la page d'exception

Dans la chaîne de traitement de la requête du client, la page XHTML est normalement le dernier maillon de la chaîne :

Image non disponible

Tous les éléments de la chaîne sont des classes Java, y compris la page XHTML. Celle-ci est en effet transformée en servlet par le conteneur de servlets, c.a.d. en une classe Java normale. Plus précisément, la page XHTML est transformée en code Java qui s'exécute au sein de la méthode suivante :

 
CacherSélectionnez

A partir de la ligne 14, on trouvera le code Java image de la page XHTML. Ce code va disposer d'un certain nombre d'objets initialisés par la méthode _jspService, ligne 1 ci-dessus :

  • ligne 1 : HttpServletRequest request : la requête en cours de traitement,
  • ligne 1 : HttpServletResponse response : la réponse qui va être envoyée au client,
  • ligne 7 : ServletContext application : un objet qui représente l'application web elle-même. Comme l'objet request, l'objet application peut avoir des attributs. Ceux-ci sont partagés par toutes les requêtes de tous les clients. Ce sont en général des attributs en lecture seule,
  • ligne 6 : HTTPSession session : représente la session du client. Comme les objets request et application, l'objet session peut avoir des attributs. Ceux-ci sont partagés par toutes les requêtes d'un même client,
  • ligne 9 : JspWriter out : un flux d'écriture vers le navigateur client. Cet objet est utile pour le débogage d'une page XHTML. Tout ce qui est écrit via out.println(texte) sera affiché dans le navigateur client.

Lorsque dans la page JSF, on écrit #{expression}, expression peut être la clé d'un attribut des objets request, session ou application ci-dessus. L'attribut correspondant est cherché successivement dans ces trois objets. Ainsi #{clé} est évaluée de la façon suivante :

  1. request.getAttribute(clé)
  2. session.getAttribute(clé)
  3. application.getAttribute(clé)

Dès qu'une valeur non null est obtenue, l'évaluation de #{clé} est arrêtée. On peut vouloir être plus précis en indiquant le contexte dans lequel l'attribut doit être cherché :

  • #{requestScope['clé']} pour chercher l'attribut dans l'objet request,
  • #{sessionScope['clé']} pour chercher l'attribut dans l'objet session,
  • #{applicationScope['clé']} pour chercher l'attribut dans l'objet application.

C'est ce qui a été fait dans la page [exception.xhtml] page . Les attributs utilisés sont les suivants :

clé domaine valeur
javax.servlet.error.status_code request cf paragraphe , page .
javax.servlet.error.exception idem idem
javax.servlet.error.request_uri idem idem
javax.servlet.error.servlet_name idem idem

Les différents messages nécessaires à la page JSF [exception.xhtml] ont été rajoutés aux fichiers de messages déjà existants :

[messages_fr.properties]

 
CacherSélectionnez

[messages_en.properties]

 
Sélectionnez
1.
2.
3.
4.
5.
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

III-H. Exemple mv-jsf2-06 : validation et conversion des saisies

III-H-1. L'application

L'application présente un formulaire de saisies. A la validation de celui-ci, le même formulaire est renvoyé en réponse, avec d'éventuels messages d'erreurs si les saisies ont été trouvées incorrectes.

Image non disponible
Image non disponible

III-H-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

Le projet [mv-jsf2-06] repose de nouveau sur une unique page [index.xhtml] [1] et son modèle [Form.java] [2]. Il continue à utiliser des messages tirés de [messages.properties] mais uniquement en français [3]. L'option de changement de langue n'est pas offerte.

III-H-3. L'environnement de l'application

Nous donnons ici la teneur des fichiers qui configurent l'application sans donner d'explications particulières. Ces fichiers permettent de mieux comprendre ce qui suit.

[]

[faces-config.xml]
CacherSélectionnez

La ligne 17 est nouvelle. Elle sera expliquée ultérieurement.

Le fichier des messages [messages_fr.properties]

[messages_fr.properties]
CacherSélectionnez

La feuille de style [styles.css] est la suivante :

[styles.css]
CacherSélectionnez

III-H-4. La page [index.xhtml] et son modèle [Form.java]

La page [index.xhtml] est la suivante :

[index.xhtml]
CacherSélectionnez

La principale nouveauté vient de la présence de balises :

  • pour afficher des messages d'erreurs <h:messages>(ligne 14), <h:message> (lignes 24, 29, 34),
  • qui posent des contraintes de validité sur les saisies <f:validateLongRange> (ligne 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
  • qui définissent un convertisseur entre la saisie et son modèle comme <f:convertDateTime>.

Le modèle de cette page est la classe [Form.java] suivante :

[Form.java]
CacherSélectionnez

La nouveauté ici est que les champs du modèle ne sont plus uniquement de type String mais de types divers.

III-H-5. Les différentes saisies du formulaire

Nous étudions maintenant successivement les différentes saisies du formulaire.

III-H-5-a. Saisies 1 à 4 : saisie d'un nombre entier

La page [index.xhtml] présente la saisie 1 sous la forme suivante :

[index.xhtml]
CacherSélectionnez

Le modèle form.saisie1 est défini comme suit dans [Form.java] :

 
CacherSélectionnez

Sur un GET du navigateur , la page [index.xhtml] associée à son modèle [Form.java] produit visuellement ce qui suit :

Image non disponible
  • la ligne 2 produit [1],
  • la ligne 3 produit [2],
  • la ligne 4 produit [3],
  • la ligne 5 produit [4].

Supposons que la saisie suivante soit faite puis validée :

Image non disponible

On obtient alors le résultat suivant dans le formulaire renvoyé par l'application :

Image non disponible
  • en [1], la saisie erronée,
  • en [2], le message d'erreur le signalant,
  • en [3], on voit que la valeur du champ Integer saisie1 du modèle n'a pas changé.

Expliquons ce qui s'est passé. Pour cela revenons au cycle de traitement d'une page JSF :

Image non disponible

Nous examinons ce cycle pour le composant :

 
CacherSélectionnez

et son modèle :

 
CacherSélectionnez
  • en [A], la page [index.xhtml] envoyée lors du GET du navigateur est restaurée. En [A], la page est telle que l'utilisateur l'a reçue. Le composant id="saisie1" retrouve sa valeur initiale "0",
  • en [B], les composants de la page reçoivent pour valeurs, les valeurs postées par le navigateur. En [B], la page est telle que l'utilisateur l'a saisie et validée. Le composant id="saisie1" reçoit pour valeur, la valeur postée "x",
  • en [C], si la page contient des validateurs et des convertisseurs explicites, ceux-ci sont exécutés. Des convertisseurs implicites sont également exécutés si le type du champ associé au composant n'est pas de type String. C'est le cas ici, où le champ form.saisie1 est de type Integer. JSF va essayer de transformer la valeur "x" du composant id="saisie1" en un type Integer. Ceci va provoquer une erreur qui va arrêter le cycle de traitement [A-F]. Cette erreur sera associée au composant id="saisie1". Via [D2], on passe ensuite directement à la phase de rendu de la réponse. La même page [index.xhtml] est renvoyée,
  • la phase [D] n'a lieu que si tous les composants d'une page ont passé la phase de conversion / validation. C'est dans cette phase, que la valeur du composant id="saisie1" sera affectée à son modèle form.saisie1.

Si la phase [C] échoue, la page est réaffichée et le code suivant est exécuté de nouveau :

 
CacherSélectionnez
Image non disponible

Le message affiché en [2] vient de la ligne 4 de [index.xhtml]. La balise <h:message for="idComposant"/> affiche le message d'erreur associé au composant désigné par l'attribut for, si erreur il y a. Le message affiché en [2] est standard et se trouve dans le fichier [javax/faces/Messages.properties] de l'archive [jsf-api.jar] :

Image non disponible

En [2], on voit que le fichier des messages existe en plusieurs variantes. Examinons le contenu de [Messages_fr.properties] :

 
CacherSélectionnez

Le fichier contient des messages divisés en catégories :

  • erreurs sur un composant, ligne 3,
  • erreurs de conversion entre un composant et son modèle, ligne 12
  • erreurs de validation lorsque des validateurs sont présents dans la page, ligne 23.

L'erreur qui s'est produite sur le composant id="saisie1" est de type erreur de conversion d'un type String vers un type Integer. Le message d'erreur associé est celui de la ligne 18 du fichier des messages.

 
CacherSélectionnez

Le message d'erreur qui a été affiché est reproduit ci-dessous :

Image non disponible

On voit que dans le message :

  • le paramètre {2} a été remplacé par l'identifiant du composant pour lequel l'erreur de conversion s'est produite,
  • la paramètre {0} a été remplacé par la saisie faite en [1] pour le composant,
  • le paramètre {1} a été remplacé par le nombre 9346.

La plupart des messages liés aux composants ont deux versions : une version résumée (summary) et une version détaillée (detail). C'est le cas des lignes 16-18 :

 
CacherSélectionnez

Le message ayant la clé _detail (ligne 2) est le message dit détaillé. L'autre est le message dit résumé. La balise <h:message> affiche par défaut le message détaillé. Ce comportement peut être changé par les attributs showSummary et showDetail. C'est ce qui est fait pour le composant d'id saisie2 :

 
CacherSélectionnez

Ligne 2, le composant saisie2 est lié au champ form.saisie2 suivant :

 
CacherSélectionnez

Le résultat obenu est le suivant :

Image non disponible
  • en [1], le message détaillé, en [2], le message résumé.

La balise <h:messages> affiche sous la forme d'une liste tous les messages d'erreurs résumés de tous les composants ainsi que les messages d'erreurs non liés à un composant. Là encore des attributs peuvent changer ce comportement par défaut :

  • showDetail : true / false pour demander ou non les messages détaillés,
  • showSummary : true / false pour demander ou non les messages résumés,
  • globalOnly : true / false pour demander à ne voir ou non que les messages d'erreurs non liées à des composants. Un tel message pourrait, par exemple, être créé par le développeur.

Le message d'erreur associé à une conversion peut être changé de diverses façons. Tout d'abord, on peut indiquer à l'application d'utiliser un autre fichier de messages. Cette modification se fait dans [faces-config.xml] :

[faces-config.xml]
CacherSélectionnez

Les lignes 3-8 définissent un fichier des messages mais ce n'est pas celui-ci qui est utilisé par les balises <h:message> et <h:messages>. Il faut utiliser la balise <message-bundle> de la ligne 9 pour le définir. La ligne 9 indique aux balises <h:message(s)> que le fichier [messages.properties] doit être exploré avant le fichier [javax.faces.Messages.properties]. Ainsi si on ajoute les lignes suivantes au fichier [messages_fr.properties] :

 
CacherSélectionnez

l'erreur renvoyée pour les composants saisie1 et saisie2 devient :

Image non disponible

L'autre façon de modifier le message d'erreur de conversion est d'utiliser l'attribut converterMessage du composant comme ci-dessous pour le composant saisie3 :

 
CacherSélectionnez

Le composant saisie3 est lié au champ form.saisie3 suivant :

 
CacherSélectionnez