IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Introduction aux frameworks JSF2, Primefaces et Primefaces mobile


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]
Cacher/Afficher le codeSé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
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSélectionnez

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

[messages_en.properties]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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']} :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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é :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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] :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

[ChangeLocale]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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],

     
    Cacher/Afficher le codeSélectionnez
  • la navigation de [page1.xhtml] vers [index.xhtml] par un lien deviendrait :
 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]

 
Cacher/Afficher le codeSé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]

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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] :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

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

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

Image non disponible

Dans la balise :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :
     
    Cacher/Afficher le codeSélectionnez
  • lignes 1-5 : le style info sert à définir le style des textes de la première colonne du tableau :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

Ligne 1, on voit le GET du navigateur.

Réponse HTTP du serveur :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

et son modèle [Form.java] :

[Form.java]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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]

 
Cacher/Afficher le codeSé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> :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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é :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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,
 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

et le modèle des quatre cases

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSélectionnez

on a désormais celui-ci :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSé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

 
Cacher/Afficher le codeSélectionnez

devient alors fonctionnellement équivalent au code JSF suivant :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

III-F-4. Le fichier des messages

Un seul message est modifié :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :
 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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]

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSélectionnez

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

Le fichier des messages [messages_fr.properties]

[messages_fr.properties]
Cacher/Afficher le codeSélectionnez

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

[styles.css]
Cacher/Afficher le codeSélectionnez

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

La page [index.xhtml] est la suivante :

[index.xhtml]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

et son modèle :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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] :

 
Cacher/Afficher le codeSé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.

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSé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]
Cacher/Afficher le codeSé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] :

 
Cacher/Afficher le codeSé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 :

 
Cacher/Afficher le codeSélectionnez

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

 
Cacher/Afficher le codeSélectionnez
  • ligne 3, l'attribut converterMessage fixe explicitement le message à afficher lors d'une erreur de conversion,
  • ligne 3, l'attribut required="true" indique que la saisie est obligatoire. Le champ ne peut rester vide. Un champ est considéré comme vide s'il ne contient aucun caractère ou s'il contient une suite d'espaces. Là encore, il existe dans [javax.faces.Messages.properties] un message par défaut :
 
Cacher/Afficher le codeSélectionnez

L'attribut requiredMessage permet de remplacer ce message par défaut. Si le fichier [messages.properties] contient les messages suivants :

 
Cacher/Afficher le codeSélectionnez

on pourra obtenir le résultat suivant :

Image non disponible

ou encore celui-ci :

Image non disponible

Vérifier qu'une saisie correspond bien à un nombre entier n'est pas toujours suffisant. Il faut parfois vérifier que le nombre saisi appartient à un intervalle donné. On utilise alors un validateur. La saisie n° 4 en donne un exemple. Son code dans [index.xhtml] est le suivant :

 
Cacher/Afficher le codeSélectionnez

Ligne 3, le composant saisie4 est lié au modèle form.saisie4 suivant :

 
Cacher/Afficher le codeSélectionnez

Lignes 3-5, la balise <h:inputText> a une balise enfant <f:validateLongRange> qui admet deux attributs facultatifs minimum et maximum. Cette balise, appelée également validateur, permet d'ajouter une contrainte à la valeur de la saisie : ce doit être non seulement un entier, mais un entier dans l'intervalle [minimum, maximum] si les deux attributs minimum et maximum sont présents, supérieure ou égale à minimum si seul l'attribut minimum est présent, inférieure ou égale à maximum si seul l'attribut maximum est présent. Le validateur <f:validateLongRange> a des messages d'erreur par défaut dans [javax.faces.Messages.properties] :

 
Cacher/Afficher le codeSélectionnez

De nouveau, il est possible de remplacer ces messages par d'autres. Il existe un attribut validatorMessage qui permet de définir un message spécifique pour le composant. Ainsi avec le code JSF suivant :

 
Cacher/Afficher le codeSélectionnez

et le message suivant dans [messages.properties] :

 
Cacher/Afficher le codeSélectionnez

on obtient le résultat suivant :

Image non disponible
III-H-5-b. Saisies 5 et 6 : saisie d'un nombre réel

La saisie des nombres réels obéit à des règles similaires à celles de la saisie des nombres entiers. Le code XHTML des saisies 5 et 6 est le suivant :

 
Cacher/Afficher le codeSélectionnez

Les éléments du modèle [Form.java] liés aux composants saisie5 et saisie6 :

 
Cacher/Afficher le codeSélectionnez

Les messages d'erreurs associés aux convertisseurs et validateurs des composants saisie5 et saisie6, dans [messages.properties] :

 
Cacher/Afficher le codeSélectionnez

Voici un exemple d'exécution :

Image non disponible
III-H-5-c. Saisie 7 : saisie d'un booléen

La saisie d'un booléen devrait être normalement faite avec une case à cocher. Si elle est faite avec un champ de saisie, la chaîne "true" est convertie en booléen true et tout autre chaîne en booléen false.

Le code XHTML de l'exemple :

 
Cacher/Afficher le codeSélectionnez

Le modèle du composant saisie7 :

 
Cacher/Afficher le codeSélectionnez

Voici un exemple de saisie et sa réponse :

Image non disponible
Image non disponible

En [1], la valeur saisie. Par conversion, cette chaîne "x" devient le booléen false. C'est ce que montre [2]. La valeur [3] du modèle n'a pas changé. Elle ne change que lorsque toutes les conversions et validations de la page ont réussi. Ce n'était pas le cas dans cet exemple.

III-H-5-d. Saisie 8 : saisie d'une date

La saisie d'une date est faite dans l'exemple avec le code XHTML suivant :

 
Cacher/Afficher le codeSélectionnez

Le composant saisie8 de la ligne 3 utilise un convertisseur java.lang.String <--> java.util.Date. Le modèle form.saisie8 associé au composant saisie8 est le suivant :

 
Cacher/Afficher le codeSélectionnez

Le composant défini par les lignes 7-9 utilise également un convertisseur mais uniquement dans le sens java.util.Date --> java.lang.String.

Le convertisseur <f:convertDateTime> admet divers attributs dont l'attribut pattern qui fixe la forme de la chaîne de caractères qui doit être transformée en date où avec laquelle une date doit être affichée.

Lors de la demande initiale de la page [index.xhtml], la ligne 8 précédente s'affiche comme suit :

Image non disponible

Les champs [1] et [2] affichent tous deux la valeur du modèle form.saisie8 :

 
Cacher/Afficher le codeSélectionnez

saisie8 prend pour valeur la date du jour. Le convertisseur utilisé dans les deux cas pour l'affichage de la date est le suivant :

 
Cacher/Afficher le codeSélectionnez

où dd (day) désigne le n° du jour, MM (Month) le n° du mois et yyyy (year) l'année. En [1], le convertisseur est utilisé pour la conversion inverse java.lang.String --> java.util.Date. La date saisie devra donc suivre le modèle "dd/MM/yyyy" pour être valide.

Il existe des messages par défaut pour les dates non valides dans [javax.faces.Messages.properties] :

 
Cacher/Afficher le codeSélectionnez

qu'on peut remplacer par ses propres messages. Ainsi dans l'exemple :

 
Cacher/Afficher le codeSélectionnez

le message affiché en cas d'erreur de conversion sera le message de clé saisie8.error suivant :

 
Cacher/Afficher le codeSélectionnez

Voici un exemple :

Image non disponible
III-H-5-e. Saisie 9 : saisie d'une chaîne de longueur contrainte

La saisie 9 montre comment imposer à une chaîne saisie d'avoir un nombre de caractères compris dans un intervalle :

 
Cacher/Afficher le codeSélectionnez

Ligne 4, le validateur <f:validateLength minimum="4" maximum="4"/> impose à la chaîne saisie d'avoir exactement 4 caractères. On peut n'utiliser que l'un des attributs : minimum pour un nombre minimal de caractères, maximum pour un nombre maximal.

Le modèle form.saisie9 du composant saisie9 de la ligne 3 est le suivant :

 
Cacher/Afficher le codeSélectionnez

Il existe des messages d'erreur par défaut pour ce type de validation :

 
Cacher/Afficher le codeSélectionnez

qu'on peut remplacer en utilisant l'attribut validatorMessage comme dans la ligne 3 ci-dessus. Le message de clé saisie9.error est le suivant :

 
Cacher/Afficher le codeSélectionnez

Voici un exemple d'exécution :

Image non disponible
III-H-5-f. Saisie 9B : saisie d'une chaîne devant se conformer à un modèle

La saisie 9B montre comment imposer à une chaîne saisie d'avoir un nombre de caractères compris dans un intervalle :

 
Cacher/Afficher le codeSélectionnez

Ligne 4, le validateur <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> impose à la chaîne saisie de correspondre au modèle d'une expression régulière, ici : une suite de 0 ou davantage d'espaces, 2 chiffres, le signe :, 2 chiffres, une suite de 0 ou davantage d'espaces.

Le modèle form.saisie9B du composant saisie9B de la ligne 3 est le suivant :

 
Cacher/Afficher le codeSélectionnez

Il existe des messages d'erreur par défaut pour ce type de validation :

 
Cacher/Afficher le codeSélectionnez

qu'on peut remplacer en utilisant l'attribut validatorMessage comme dans la ligne 3 ci-dessus. Le message de clé saisie9.error est le suivant :

 
Cacher/Afficher le codeSélectionnez

Voici un exemple d'exécution :

Image non disponible
III-H-5-g. Saisie 10 : écrire une méthode de validation spécifique

Résumons : JSF permet de vérifier parmi les valeurs saisies, la validité des nombres (entiers, réels), des dates, la longueur des chaînes et la conformité d'une saisie vis à vis d'une expression régulière. JSF permet d'ajouter aux validateurs et convertisseurs existants ses propres validateurs et convertisseurs. Ce point n'est pas abordé ici mais on pourra lire [ref2] pour l'approfondir.

Nous présentons ici une autre méthode : celle qui consiste à valider une donnée saisie par une méthode du modèle du formulaire. C'est l'exemple suivant :

 
Cacher/Afficher le codeSélectionnez

Le modèle form.saisie10 associé au composant saisie10 de la ligne 3 est le suivant :

 
Cacher/Afficher le codeSélectionnez

On veut que le nombre saisi soit <1 ou >7. On ne peut le vérifier avec les validateurs de base de JSF. On écrit alors sa propre méthode de validation du composant saisie10. On l'indique avec l'attribut validator du composant à valider :

 
Cacher/Afficher le codeSélectionnez

Le composant saisie10 est validé par la méthode form.validateSaisie10. Celle-ci est la suivante :

 
Cacher/Afficher le codeSélectionnez

La signature d'une méthode de validation est obligatoirement celle de la ligne 1 :

  • FacesContext context : contexte d'exécution de la page - donne accès à diverses informations, notamment aux objets HttpServletRequest request et HttpServletResponse response,
  • UIComponent component : le composant qu'il faut valider. La balise <h:inputText> est représenté par un composant de type UIInput dérivé de UIComponent. Ici c'est ce composant UIInput qui est reçu en second paramètre,
  • Object value : la valeur saisie à vérifier, transformée dans le type de son modèle. Il est important de comprendre ici que si la conversion String -> type du modèle a échoué, alors la méthode de validation n'est pas exécutée. Lorsqu'on arrive dans la méthode validateSaisie10, c'est que la conversion String --> Integer a réussi. Le troisième paramètre est alors de type Integer.
  • ligne 2 : la valeur saisie est transformée en type int,
  • ligne 3 : on vérifie que la valeur saisie est <1 ou >7. Si c'est le cas, la validation est terminée. Si ce n'est pas le cas, le validateur doit signaler l'erreur en lançant une exception de type ValidatorException.

La classe ValidatorException a deux constructeurs :

  • Image non disponible

    le constructeur [1] a pour paramètre un message d'erreur de type FacesMessage. Ce type de message est celui affiché par les balises <h:messages> et <h:message>,

  • le constructeur [2] permet de plus d'encapsuler la cause de type Throwable ou dérivé de l'erreur.

Il nous faut construire un message de type FacesMessage. Cette classe a divers constructeurs :

Image non disponible

Le constructeur [1] définit les propriétés d'un objet FacesMessage :

  • FacesMessage.Severity severity : un niveau de gravité pris dans l'énumération suivante : SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
  • String summary : la version résumée du message d'erreur - est affichée par les balises <h:message showSummary="true"> et <h:messages>,
  • String detail : la version détaillée du message d'erreur - est affichée par les balises <h:message> et <h:messages showDetail="true">.

N'importe lequel des constructeurs peut-être utilisé, les paramètres manquants pouvant être fixés ultérieurement par des méthodes set.

Le constructeur [1] ne permet pas de désigner un message qui serait dans un fichier de messages internationalisé. C'est évidemment dommage. [ref2] comblent cette lacune dans leur livre "Core JavaServer Faces" avec la classe utilitaire com.corejsf.util.Messages. C'est cette classe qui est utilisée ligne 4 du code Java pour créer le message d'erreur. Elle ne contient que des méthodes statiques dont la méthode getMessage utilisée ligne 4 :

 
Cacher/Afficher le codeSélectionnez

La méthode getMessage admet trois paramètres :

  • String bundleName : le nom d'un fichier de messages sans son suffixe .properties mais avec son nom de paquetage. Ici, notre premier paramètre pourrait être messages pour désigner le fichier [messages.properties]. Avant d'utiliser le fichier désigné par le premier paramètre, getMessage essaie d'utiliser le fichier des messages de l'application, s'il y en a un. Ainsi si dans [faces-config.xml] on a déclaré un fichier de messages avec la balise :
 
Cacher/Afficher le codeSélectionnez

on peut passer null comme premier paramètre à la méthode getMessage. C'est ce qui a été fait ici (cf [web.xm], page ),

  • String resourceId : la clé du message à exploiter dans le fichier des messages. Nous avons vu qu'un message pouvait avoir à la fois une version résumée et une version détaillée. resourceId est l'identifiant de la version résumée. La version détaillée sera recherchée automatiquement avec la clé resourceId_detail. Ainsi, aurons-nous deux messages dans [messages.properties] pour l'erreur sur la saisie n° 10 :
 
Cacher/Afficher le codeSélectionnez

Le message de type FacesMessage produit par la méthode Messages.getMessage inclut à la fois les versions résumée et détaillée si elles ont été trouvées. Les deux versions doivent être présentes sinon on récupère une exception de type [NullPointerException],

  • Object[] params : les paramètres effectifs du message si celui-ci a des paramètres formels {0}, {1}… Ces paramètres formels seront remplacés par les éléments du tableau params.

Revenons au code de la méthode de validation du composant saisie10 :

 
Cacher/Afficher le codeSélectionnez
  • en [4], le message de type FacesMessage est créé à l'aide de la méthode statique Messages.getMessage,
  • en [5], on fixe le niveau de gravité du message,
  • en [6], on lance une exception de type ValidatorException avec le message construit précédemment. La méthode de validation a été appelée par le code XHTML suivant :
 
Cacher/Afficher le codeSélectionnez

Ligne 3, la méthode de validation est exécutée pour le composant d'id saisie10. Aussi le message d'erreur produit par la méthode validateSaisie10 est-il associé à ce composant et donc affiché par la ligne 4 (attribut for="saisie10"). C'est la version détaillée qui est affichée par défaut par la balise <h:message>.

Voici un exemple d'exécution :

Image non disponible
III-H-5-h. Saisies 11 et 12 : validation d'un groupe de composants

Jusqu'à maintenant, les méthodes de validation rencontrées ne validaient qu'un unique composant. Comment faire si la validation souhaitée concerne plusieurs composants ? C'est ce que nous voyons maintenant. Dans le formulaire :

Image non disponible

nous voulons que les saisies 11 et 12 soient deux nombres entiers dont la somme soit égale à 10.

Le code JSF sera le suivant :

 
Cacher/Afficher le codeSélectionnez

et le modèle associé :

 
Cacher/Afficher le codeSélectionnez

Ligne 3 du code JSF, on utilise les techniques déjà présentées pour vérifier que la valeur saisie pour le composant saisie11 est bien un entier. Il en est de même, ligne 11, pour le composant saisie12. Pour vérifier que saisie11 + saisie12 =10, on pourrait construire un validateur spécifique. C'est la solution à préférer. De nouveau on lira [ref2] pour la découvrir. Nous suivons ici une autre démarche.

La page [index.xhtml] est validée par un bouton [Valider] dont le code JSF est le suivant :

 
Cacher/Afficher le codeSélectionnez

où le message msg['submit'] est le suivant :

 
Cacher/Afficher le codeSélectionnez

On voit ligne 3, que la méthode form.submit va être exécutée pour traiter le clic sur le bouton [Valider]. Celle-ci est la suivante :

 
Cacher/Afficher le codeSélectionnez

Il est important de comprendre que lorsque la méthode submit s'exécute :

  • tous les validateurs et convertisseurs du formulaire ont été exécutés et réussis,
  • les champs du modèle [Form.java] ont reçu les valeurs postées par le client.

En effet, revenons au cycle de traitement d'un POST JSF :

Image non disponible

La méthode submit est un gestionnaire d'événement. Elle gère l'événement clic sur le bouton [Valider]. Comme tous les gestionnaires d'événement, elle s'exécute dans la phase [E], une fois que tous les validateurs et convertisseurs ont été exécutés et réussis [C] et que le modèle a été mis à jour avec les valeurs postées [D]. Il n'est donc plus question ici de lancer des exceptions de type [ValidatorException] comme nous l'avons fait précédemment. Nous nous contenterons de renvoyer le formulaire avec des messages d'erreur :

Image non disponible

En [1], nous alerterons l'utilisateur et en [2] et [3], nous mettrons un signe d'erreur. Dans le code JSF, le message [1] sera obtenu de la façon suivante :

 
Cacher/Afficher le codeSélectionnez

En ligne 2, la balise <h:messages> affiche par défaut la version résumée des messages d'erreurs de toutes les saisies erronées de composants du formulaire ainsi que tous les messages d'erreur non liés à des composants. L'attribut globalOnly="true" limite l'affichage à ces derniers.

Les messages [2] et [3] sont affichés avec de simples balises <h:outputText> :

 
Cacher/Afficher le codeSélectionnez

Lignes 4-7, le composant saisie11 a deux messages d'erreur possibles :

  • celui indiquant une conversion erronée ou une absence de donnée. Ce message généré par JSF lui-même sera contenu dans un type FacesMessage et affiché par la balise <h:message> de la ligne 5,
  • celui que nous allons générer si saisie11 + saisie12 n'est pas égal à 10. Il sera affiché par la ligne 6. Le message d'erreur sera contenu dans le modèle form.errorSaisie11.

Les deux messages correspondent à des erreurs qui ne peuvent se produire en même temps. La vérification saisie11 + saisie12 = 10 est faite dans la méthode submit qui ne s'exécute que s'il ne reste aucune erreur dans le formulaire. Lorsqu'elle s'exécutera, le composant saisie11 aura été vérifié et son modèle form.saise11 aura reçu sa valeur. Le message de la ligne 5 ne pourra plus être affiché. Inversement, si le message de la ligne 5 est affiché, il reste alors au moins une erreur dans le formulaire et la méthode submit ne s'exécutera pas. Le message de la ligne 6 ne sera pas affiché. Afin que les deux messages d'erreur possibles soient dans la même colonne du tableau, ils ont été rassemblés dans une balise <h:panelGroup> (lignes 4 et 7).

La méthode submit est la suivante :

 
Cacher/Afficher le codeSélectionnez
  • ligne 4 : la méthode submit appelle la méthode validateForm pour faire les dernières validations,
  • ligne 11 : on vérifie si saisie11+saisie12=10,
  • si ce n'est pas le cas, lignes 13-14, on crée un message de type FacesMessage avec le message d'id saisies11et12.incorrectes. Celui-ci est le suivant :
 
Cacher/Afficher le codeSélectionnez
  • le message ainsi construit est ajouté (lignes 15-16) à la liste des messages d'erreur de l'application. Ce message n'est pas lié à un composant particulier. C'est un message global de l'application. Il sera affiché par la balise <h:messages globalOnly="true"/> présentée plus haut,
  • ligne 18 : on crée un nouveau message de type FacesMessage avec le message d'id error.sign. Celui-ci est le suivant :
 
Cacher/Afficher le codeSélectionnez

Nous avons dit que la méthode statique [Messages.getMessage] construisait un message de type FacesMessage avec une version résumée et une version détaillée si elles existaient. Ici, seule la version résumée du message error.sign existe. On obtient la version résumée d'un message m, par m.getSummary(). Lignes 19 et 20, la version résumée du message error.sign est mise dans les champs errorSaisie11 et errorSaisie12 du modèle. Ils seront affichés par les balises JSF suivantes :

 
Cacher/Afficher le codeSélectionnez
  • lignes 22-23 : si la propriété saisie11+saisie12=10 est vérifiée, alors les deux champs errorSaisie11 et errorSaisie12 du modèle sont vidés afin qu'un éventuel message d'erreur précédent soit effacé. Il faut se rappeler ici que le modèle est conservé entre les requêtes, dans la session du client.

Voici un exemple d'exécution :

Image non disponible

On remarquera dans la colonne [1] que le modèle a reçu les valeurs postées, ce qui montre que toutes les opérations de validation et de conversion entre les valeurs postées et le modèle ont réussi. Le gestionnaire d'événement form.submit qui gère le clic sur le bouton [Valider] a pu ainsi s'exécuter. C'est lui qui a produit les messages affichés en [2] et [3]. On voit que le modèle a été mis à jour alors même que le formulaire a été refusé et renvoyé au client. On pourrait vouloir que le modèle ne soit pas à mis à jour dans un tel cas. En effet, en imaginant que l'utilisateur annule sa mise à jour avec le bouton [Annuler] [4], on ne pourra pas revenir au modèle initial sauf à l'avoir mémorisé.

III-H-5-i. POST d'un formulaire sans vérification des saisies

Considérons le formulaire ci-dessus et supposons que l'utilisateur ne comprenant pas ses erreurs veuille abandonner la saisie du formulaire. Il va alors utiliser le bouton [Annuler] généré par le code JSF suivant :

 
Cacher/Afficher le codeSélectionnez

Ligne 4, le message msg['cancel'] est le suivant :

 
Cacher/Afficher le codeSélectionnez

La méthode form.cancel associée au bouton [Annuler] ne sera exécutée que si le formulaire est valide. C'est ce que nous avons montré pour la méthode form.submit associée au bouton [Valider]. Si l'utilisateur veut annuler la saisie du formulaire, il est bien sûr inutile de vérifier la validité de ses saisies. Ce résultat est obtenu avec l'attribut immediate="true" qui indique à JSF d'exécuter la méthode form.cancel sans passer par la phase de validation et de conversion. Revenons au cycle de traitement du POST JSF :

Image non disponible

Les événements des composants d'action <h:commandButton> et <h:commandLink> ayant l'attribut immediate="true" sont traités dans la phase [C] puis le cycle JSF passe directement à la phase [E] de rendu de la réponse.

La méthode form.cancel est la suivante :

 
Cacher/Afficher le codeSélectionnez

Si on utilise le bouton [Annuler] dans le formulaire précédent, on obtient en retour la page suivante :

Image non disponible
  • on obtient de nouveau le formulaire car le gestionnaire d'événement form.cancel rend la clé de navigation null. La page [index.xhtml] est donc renvoyée,
  • le modèle [Form.java] a été modifié par la méthode form.cancel. Ceci est reflété par la colonne [2] qui affiche ce modèle,
  • la colonne [3], elle, reflète la valeur postée pour les composants.

Revenons sur le code JSF du composant saisie1 [4] ;

 
Cacher/Afficher le codeSélectionnez

Ligne 4, la valeur du composant saisie1 est liée au modèle form.saisie1. Cela entraîne plusieurs choses :

  • lors d'un GET de [index.xhtml], le composant saisie1 affichera la valeur du modèle form.saisie1,
  • lors d'un POST de [index.xhtml], la valeur postée pour le composant saisie1 n'est affectée au modèle form.saisie1 quesi l'ensemble des validations et conversions du formulaire réussissent. Que le modèle ait été mis à jour ou non par les valeurs postées, si le formulaire est renvoyé à l'issue du POST, les composants affichent la valeur qui a été postée et non pas la valeur du modèle qui leur est associé. C'est ce que montre la copie d'écran ci-dessus, où les colonnes [2] et [3] n'ont pas les mêmes valeurs.

III-I. Exemple mv-jsf2-07 : événements liés au changement d'état de composants JSF

III-I-1. L'application

L'application montre un exemple de POST réalisé sans l'aide d'un bouton ou d'un lien. Le formulaire est le suivant :

Image non disponible

Le contenu de la liste combo2 [2] est liée à l'élément sélectionné dans le combo1 [1]. Lorsqu'on change la sélection dans [1], un POST du formulaire est réalisé au cours duquel le contenu de combo2 est modifié pour refléter l'élément sélectionné dans [1], puis le formulaire renvoyé. Au cours de ce POST, aucune validation n'est faite.

III-I-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

On a un unique formulaire [index.xhtml] avec son modèle [Form.java].

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

Le fichier des messages [messages_fr.properties] :

 
Cacher/Afficher le codeSélectionnez

La feuille de style [styles.css] :

[styles.css]
Cacher/Afficher le codeSélectionnez

III-I-4. Le formulaire [index.xhtml]

Le formulaire [index.xhtml] est le suivant :

[index.xhtml]
Cacher/Afficher le codeSélectionnez

La nouveauté réside dans le code de la liste combo1, lignes 24-26. De nouveaux attributs apparaissent :

  • onchange : attribut HTML - déclare une fonction ou du code Javascript qui doit être exécuté lorsque l'élément sélectionné dans combo1 change. Ici, le code Javascript submit() poste le formulaire au serveur,
  • valueChangeListener : attribut JSF - déclare le nom de la méthode à exécuter côté serveur lorsque l'élément sélectionné dans combo1 change. Au total, il y a deux méthodes exécutées : l'une côté client, l'autre côté serveur,
  • immediate=true : attribut JSF - fixe le moment où doit être exécuté le gestionnaire d'événement côté serveur : après que le formulaire ait été reconstitué comme l'utilisateur l'a saisi mais avant les contrôles de validité des saisies. On veut ici, remplir la liste combo2 en fonction de l'élément sélectionné dans la liste combo1, même si par ailleurs dans le formulaire il peut y avoir des saisies erronées. Voici un exemple :
    Image non disponible
  • en [1], une première saisie,
  • en [2], on passe l'élément sélectionné de combo1 de A à B.

Le résultat obtenu est le suivant :

Image non disponible

Le POST a eu lieu. Le contenu de combo2 [2] a été adapté à l'élément sélectionné dans combo1 [1] bien que la saisie [3] était incorrecte. C'est l'attribut immediate=true qui a fait que la méthode form.combo1ChangeListener a été exécutée avant les contrôles de validité. Sans cet attribut, elle n'aurait pas été exécutée car le cycle de traitement se serait arrêté aux contrôles de validité à cause de l'erreur en [3].

Les messages associés au formulaire sont les suivants dans [messages.properties] :

 
Cacher/Afficher le codeSélectionnez

La durée de vie de [Form.java] est fixée à session :

 
Cacher/Afficher le codeSélectionnez

Ligne 6, on fixe la portée du bean à session. La première version du bean avait la portée request. Des erreurs incompréhensibles apparaissaient alors. Le changement de portée les a fait disparaître. Je n'ai pas trouvé d'explications à ces erreurs.

III-I-5. Le modèle [Form.java]

Le modèle [Form.java] est le suivant :

[Form.java]
Cacher/Afficher le codeSélectionnez

Relions le formulaire [index.xhtml] à son modèle [Form.java] :

La liste combo1 est générée par le code JSF suivant :

 
Cacher/Afficher le codeSélectionnez

Elle obtient ses éléments par la méthode getCombo1Items de son modèle (ligne 2). Celle-ci est définie lignes 28-35 du code Java. Elle génère une liste de trois éléments {"A","B","C"}.

La liste combo2 est générée par le code JSF suivant :

 
Cacher/Afficher le codeSélectionnez

Elle obtient ses éléments par la méthode getCombo2Items de son modèle (ligne 2). Celle-ci est définie lignes 37-44 du code Java. Elle génère une liste de cinq éléments {"X1","X2","X3","X4","X5"} où X est l'élément combo1Label de la ligne 16. Donc lors de la génération initiale du formulaire, la liste combo2 contient les éléments {"A1","A2","A3","A4","A5"}.

Lorsque l'utilisateur va changer l'élément sélectionné dans la liste combo1,

  • l'événement onchange="submit();" va être traité par le navigateur client. Le formulaire va donc être posté au serveur,
  • côté serveur, JSF va détecter que le composant combo1 a changé de valeur. La méthode combo1ChangeListener des lignes 47-54 va être exécutée. Une méthode de type ValueChangeListener reçoit en paramètre un objet de type javax.faces.event.ValueChangeEvent. Cet objet permet d'obtenir l'ancienne et la nouvelle valeur du composant qui a changé de valeur avec les méthodes suivantes :
Image non disponible

Ici le composant est la liste combo1 de type UISelectOne. Sa valeur est de type String.

  • ligne 51 du modèle Java : la nouvelle valeur de combo1 est mémorisée dans combo1Label qui sert à générer les éléments de la liste combo2,
  • ligne 53 : on rend la réponse. Il faut se rappeler ici que le gestionnaire combo1ChangeListener est exécuté avec l'attribut immediate="true". Il est donc exécuté après la phase où l'arbre des composants de la page a été mis à jour avec les valeurs postées et avant le processus de validation des valeurs postées. Or on veut éviter ce processus de validation parce que la liste combo2 doit être mise à jour même si par ailleurs dans le formulaire, il reste des saisies erronées. On demande donc à ce que la réponse soit envoyée tout de suite sans passer par la phase de validation des saisies.
  • le formulaire va être renvoyé tel qu'il a été saisi. Cependant, les éléments des listes combo1 et combo2 ne sont pas des valeurs postées. Elles vont être générées de nouveau par appel aux méthodes getCombo1Items et getCombo2Items. Cette dernière méthode va alors utiliser la nouvelle valeur de combo1Label fixée par combo1ChangeListener et les éléments de la liste combo2 vont changer.

III-I-6. Le bouton [Raz]

Nous voulons avec le bouton [Raz] remettre le formulaire dans un état initial comme montré ci-dessous :

Image non disponible
Image non disponible

En [1], le formulaire avant le POST du bouton [Raz], en [2] le résultat du POST.

Bien que fonctionnellement simple, la gestion de ce cas d'utilisation s'avère assez complexe. On peut essayer diverses solutions, notamment celle utilisée pour le bouton [Annuler] de l'exemple précédent :

 
Cacher/Afficher le codeSélectionnez

où la méthode form.raz est la suivante :

 
Cacher/Afficher le codeSélectionnez

Le résultat obtenu par le bouton [Raz] dans l'exemple précédent est alors le suivant :

Image non disponible

La colonne [1] montre que la méthode form.raz a été exécutée. Cependant la colonne [1] continue à afficher les valeurs postées :

  • pour combo1, la valeur postée était "B". Cet élément est donc sélectionné dans la liste,
  • pour combo2, la valeur postée était "B5". Du fait de l'exécution de form.raz, les éléments {"B1"…, "B5"} de combo2 ont été changés en {"A1"…, "A5"}. L'élément "B5" n'existe plus et ne peut donc être sélectionné. C'est alors le premier élément de la liste qui est affiché,
  • pour saisie1, la valeur postée était 10.

Ceci est le fonctionnement normal avec l'attribut immediate="true". Pour avoir un résultat différent, il faut poster les valeurs qu'on veut voir dans le nouveau formulaire même si l'utilisateur a lui saisi d'autres valeurs. On réalise cela avec un peu de code Javascript côté client. Le formulaire devient le suivant :

 
Cacher/Afficher le codeSélectionnez
  • ligne 10, l'attribut onclick='raz()' indique d'exécuter la fonction Javascript raz lorsque l'utilisateur clique sur le bouton [Raz],
  • ligne 3 : on affecte la valeur "A" à l'élément HTML de nom 'formulaire:combo1'. Les différents éléments de la ligne 3 sont les suivants :
  • document : page affichée par le navigateur,
  • document.forms : ensemble des formulaires du document,
  • document.forms['formulaire'] : le formulaire avec l'attribut name="formulaire",
  • documents.forms['formulaire'].elements : ensemble des éléments du formulaire ayant l'attribut name="formulaire",
  • document.forms['formulaire'].elements['formulaire:combo1'] : élément du formulaire ayant l'attribut name="formulaire:combo1"
  • document.forms['formulaire'].elements['formulaire:combo1'].value : valeur qui sera postée par l'élément du formulaire ayant l'attribut name="formulaire:combo1".

Pour connaître les attributs name des différents éléments de la page affichée par le navigateur, on pourra consulter son code source ( ci-dessous avec IE7) :

Image non disponible
 
Cacher/Afficher le codeSélectionnez

Ceci expliqué, on peut comprendre que dans le code Javascript de la fonction raz :

  • la ligne 3 fait que la valeur postée pour le composant combo1 sera la chaîne A,
  • la ligne 4 fait que la valeur postée pour le composant combo2 sera la chaîne A1,
  • la ligne 5 fait que la valeur postée pour le composant saisie1 sera la chaîne 0.

Ceci fait, le POST du formulaire, associé à tout bouton de type <h:commandButton> (ligne 10) va se produire. La méthode form.raz va être exécutée et le formulaire renvoyé tel qu'il a été posté. On obtient alors le résultat suivant :

Image non disponible

Ce résultat cache beaucoup de choses. Les valeurs "A","A1","0" des composants combo1, combo2, saisie1 sont postées au serveur. Supposons que la valeur précédente de combo1 était "B". Alors il y a un changement de valeur du composant combo1 et la méthode form.combo1ChangeListener devrait être exécutée elle aussi. On a deux gestionnaires d'événements ayant l'attribut immediate="true". Vont-ils être exécutés tous les deux ? Si oui, dans quel ordre ? L'un seulement ? Si oui lequel ?

Pour en savoir plus, nous créons des logs dans l'application :

 
Cacher/Afficher le codeSélectionnez
  • ligne 16 : un générateur de logs est créé. Le paramètre de getLogger permet de différencier les origines des logs. Ici, le logueur s'appelle forms.Form,
  • ligne 21 : on logue le passage dans la méthode combo1ChangeListener,
  • ligne 30 : on logue le passage dans la méthode raz.

Quels sont les logs produits par le bouton [Raz] ou le changement de valeur de combo1 ? Considérons divers cas :

  • on utilise le bouton [Raz] alors que l'élément sélectionné dans combo1 est "A". "A" est donc la dernière valeur du composant combo1. Nous avons vu que le bouton [Raz] exécutait une fonction Javascript qui postait la valeur "A" pour le composant combo1. Le composant combo1 ne change donc pas de valeur. Les logs montrent alors que seule la méthode form.raz est exécutée :
    Image non disponible
  • on utilise le bouton [Raz] alors que l'élément sélectionné dans combo1 n'est pas "A". Le composant combo1 change donc de valeur : sa dernière valeur n'était pas "A" et le bouton [Raz] va lui poster la valeur "A". Les logs montrent alors que deux méthodes sont exécutées. Dans l'ordre : combo1ChangeListener, raz :
    Image non disponible
  • on change la valeur du combo1 sans utiliser le bouton [Raz]. Les logs montrent que seule la méthode combo1ChangeListener est exécutée :
Image non disponible

III-J. Exemple mv-jsf2-08 : la balise <h:dataTable>

III-J-1. L'application

L'application montre une liste de personnes avec la possibilité d'en supprimer :

Image non disponible
  • en [1], une liste de personnes,
  • en [2], les liens qui permettent de les supprimer.

III-J-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

On a un unique formulaire [index.xhtml] avec son modèle [Form.java].

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

Le fichier de configuration [faces-config.xml] :

[faces-config.xml]
Cacher/Afficher le codeSélectionnez

Le fichier des messages [messages_fr.properties] :

 
Cacher/Afficher le codeSélectionnez

La feuille de style [styles.css] :

[styles.css]
Cacher/Afficher le codeSélectionnez

III-J-4. Le formulaire [index.xhtml] et son modèle [Form.java]

Rappelons la vue associée à la page [index.xhtml] :

Image non disponible

Le formulaire [index.xhtml] est le suivant :

[index.xhtml]
Cacher/Afficher le codeSélectionnez

Ligne 14, la balise <h:dataTable> utilise le champ #{form.personnes} comme source de données. C'est le suivant :

 
Cacher/Afficher le codeSélectionnez

La classe [Personne] est la suivante :

 
Cacher/Afficher le codeSélectionnez

Revenons au contenu de la balise <h:dataTable> :

 
Cacher/Afficher le codeSélectionnez
  • l'attribut var="personne" fixe le nom de la variable représentant la personne courante à l'intérieur de la balise <h:datatable>,
  • l'attribut headerClass="headers" fixe le style des titres des colonnes du tableau,
  • l'attribut columnClasses="…." fixe le style de chacune des colonnes du tableau.

Examinons l'une des colonnes du tableau et voyons comment elle est construite :

Image non disponible

Le code XHTML de la colonne Id est le suivant :

 
Cacher/Afficher le codeSélectionnez
  • lignes 3-5 : la balise <f:facet name="header"> définit le titre de la colonne,
  • ligne 4 : le titre de la colonne est pris dans le fichier des messages,
  • ligne 6 : personne fait référence à l'attribut var de la balise <h:dataTable …> (ligne 1). On écrit donc l'id de la personne courante.
 
Cacher/Afficher le codeSélectionnez
  • lignes 3-7 : la colonne id du tableau,
  • lignes 8-13 : la colonne nom du tableau,
  • lignes 14-19 : la colonne prénom du tableau.

Maintenant, examinons la colonne des liens [Retirer] :

Image non disponible

Cette colonne est générée par le code suivant :

 
Cacher/Afficher le codeSélectionnez

Le lien [Retirer] est généré par les lignes 4-6. Lorsque le lien est cliqué, la méthode [Form].retirerPersonne va être exécutée. Il est temps d'examiner la classe [Form.java] :

 
Cacher/Afficher le codeSélectionnez
  • lignes 18-24 : le contructeur initialise la liste des personnes de la ligne 14,
  • ligne 10 : parce que cette liste doit vivre au fil des requêtes, la portée du bean est la session.

Lorsque la méthode [retirerPersonne] de la ligne 26 est exécutée, le champ de la ligne 15 a été initialisé par l'id de la personne dont on a cliqué le lien [Retirer] :

 
Cacher/Afficher le codeSélectionnez

La balise <f:setPropertyActionListener> permet de transférer des informations au modèle. Ici la valeur de l'attribut value est copiée dans le champ du modèle identifié par l'attribut target. Ainsi l'id de la personne courante, celle qu'on doit supprimer de la liste des personnes, est copiée dans le champ [Form].personneId via le getter de ce champ. Ceci est fait avant l'exécution de la méthode référencée par l'attribut action de la ligne 1.

Lignes 26-43, la méthode [supprimerPersonne] supprime la personne dont l'id est égal à personneId.

III-K. Exemple mv-jsf2-09 : mise en page d'une application JSF

III-K-1. L'application

L'application montre comment mettre en page d'une application JSF à deux vues :

Image non disponible

L'application a deux vues :

  • en [1], la page 1,
  • en [2], la page 2.

On peut naviguer entre les deux pages. Ce qu'on veut montrer ici, c'est que les pages 1 et 2 partagent une mise en forme commune comme il apparaît sur les copies d'écran ci-dessus.

III-K-2. Le projet Netbeans

Le projet Netbeans de l'application est le suivant :

Image non disponible

L'application n'a que des pages XHTML. Il n'y a pas de modèle Java associé.

III-K-3. La page [layout.xhtml]

La page [layout.xhtml] fixe la forme des pages de l'application :

[layout.xhtml]
Cacher/Afficher le codeSélectionnez

Ligne 7, apparaît un nouvel espace de noms ui. Cet espace de noms contient les balises qui permettent de mettre en forme les pages d'une application. Les balises de cet espace sont utilisées aux lignes 17, 22, 25, 32.

La page [layout.xhtml] affiche des informations dans un tableau HTML (ligne 14). On peut demander cette page avec un navigateur :

  • Image non disponible en [1], l'URL demandée.

La zone [2] a été générée par le code XHTML suivant :

 
Cacher/Afficher le codeSélectionnez

La balise <ui:include> de la ligne 6 permet d'inclure dans la page, un code XHTML externe. Le fichier [entete.xhtml] est le suivant :

[entete.xhtml]
Cacher/Afficher le codeSélectionnez

C'est tout le code des lignes 3-8 qui va être inséré dans [layout.xhtml]. Ainsi des balises <html> et <body> vont être insérées dans une balise <td>. Ca ne provoque pas d'erreurs. Donc, les pages insérées par <ui:include> sont des pages XHTML complètes. D'un point de vue visuel, seule la ligne 6 va avoir un effet. Les balises <html> et <body> sont présentes pour des raisons syntaxiques.

La zone [3] a été générée par le code XHTML suivant :

 
Cacher/Afficher le codeSélectionnez

La balise <ui:include> de la ligne 5 inclut le fichier [menu.xhtml] suivant :

[menu.xhtml]
Cacher/Afficher le codeSélectionnez

La zone [4] a été générée par le code XHTML suivant :

 
Cacher/Afficher le codeSélectionnez

La balise <ui:include> de la ligne 6 inclut le fichier [basdepage.xhtml] suivant :

[basdepage.xhtml]
Cacher/Afficher le codeSélectionnez

La zone [5] a été générée par le code XHTML suivant :

 
Cacher/Afficher le codeSélectionnez

La balise <ui:insert> de la ligne 5 définit une zone appelée contenu. C'est une zone qui peut recevoir un contenu variable. Nous verrons comment. Lorsque nous avons demandé la page [layout.xhtml], aucun contenu n'a été défini pour la zone appelée contenu. Dans ce cas, le contenu de la balise <ui:insert> des lignes 4-6 est utilisé. La ligne 5 est donc affichée.

III-K-4. La page [page1.xhtml]

La page [layout.xhtml] n'est pas destinée à être visualisée. Elle sert de modèle aux pages [page1.xhtml] et [page2.xhtml]. On parle de template de pages. La page [page1.xhtml] est la suivante :

[page1.xhtml]
Cacher/Afficher le codeSélectionnez
  • ligne 6, on utilise l'espace de noms ui,
  • ligne 7, on indique que la page est associée au template [layout.xhtml] à l'aide d'une balise <ui:composition>,
  • ligne 8, cette association fait que chaque balise <ui:define> sera associée à une balise <ui:insert> du template utilisé, ici [layout.xhtml]. Le lien se fait par l'attribut name des deux balises. Ils doivent être identiques.

La page affichée est [layout.xhtml] où le contenu de chaque balise <ui:insert> est remplacé par le contenu de la balise <ui:define> de la page demandée. Ici, tout se passe comme si la page affichée était :

 
Cacher/Afficher le codeSélectionnez

Les lignes 25-26 de [page1.xhtml] ont été insérées en lieu et place de la balise <ui:insert> de [layout.xml].

La page [page2.xhtml] est analogue à [page1.xhtml] :

[page2.xhtml]
Cacher/Afficher le codeSélectionnez

III-L. Conclusion

L'étude qui vient d'être faite de JSF 2 est loin d'être exhaustive. Mais elle est suffisante pour comprendre les exemples qui vont suivre. Pour approfondir, on lira [ref2].

III-M. Les tests avec Eclipse

Montrons comment procéder aux tests des projets Maven avec SpringSource Tool Suite :

Image non disponible
  • en [1], on importe un projet Maven [2] qu'on désigne avec le bouton [3]. On prend ici le projet Maven [mv-jsf2-09] pour Eclipse
  • en [4], le projet importé a bien été reconnu comme un projet Maven [5],
    Image non disponible
  • en [6], le projet importé dans l'explorateur de projets,
  • en [7], on l'exécute sur un serveur [8] Tomcat [9],
    Image non disponible
  • en [10], Tomcat 7 a été lancé,
  • en [11], la page d'accueil du projet [mv-jsf2-09] [11] s'affiche dans un navigateur interne à Eclipse.

précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Serge Tahé. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.