JavaServer Faces - JSF

Chapitres traités   

Pour afficher graphiquement les informations provenant du serveur, nous avons besoin d'une interface utilisateur. Les applications utilisant les interfaces pour interagir avec l'utilisateur sont de différents types : applications de bureau, applications web s'exécutant dans un navigateur ou applications mobiles sur un terminal portable. Nous nous intéressons ici sur les interfaces web.

Initialement, le World Wide Web était un moyen de partager des documents écrits en HTML. Le protocole HTTP a été conçu pour véhiculer ces documents, qui étaient à l'origine essentiellement statiques (leur contenu n'évoluait pas beaucoup au cours du temps).

  1. Les pages statiques sont composées de HTML pur contenant éventuellement des graphiques eux aussi statiques (JPG, PNG, par exemple).
  2. Les pages dynamiques sont en revanche composées en temps réel (à la volée) à partir de données calculées à partir d'informations fournies par l'utilisateur.

Pour créer un contenu dynamique, il faut analyser les requêtes HTTP, comprendre leur signification et créer des réponses dans un format que le navigateur saura traiter. Les servlets simplifie le processus en fournissant une vue orientée objet du monde HTTP (HttpRequest, HttpResponse, etc.).

Cependant, le modèle des servlets était de trop bas niveau et c'est la raison pour laquelle, les pages JSP (JavaServer Pages) ont ensuite pris le relais pour simplifier la création des pages web dynamiques. En coulisse, une JSP est une servlet, sauf qu'elle est écrite essentiellement en HTML - avec un peu de Java pour effectuer les traitements.

Ceci dit, les servlets peuvent toujours être utiles dans le cas de traitements spécifiques qui ne consistent pas à créer, pour la réponse au navigateur, de page web en format HTML. Nous pourrions, par exemple, fabriquer une vignette correspondant à une photo grand format archivée sur le serveur.

JSF (JavaServer Faces, ou simplement Faces) a été créé en réponse à certaines limitations de JSP et utilise un autre modèle consistant à porter des composants graphiques vers le Web. Inspiré par le modèle Swing, JSF permet aux développeurs de penser en termes de composants, d'événements, de beans gérés et de leur interactions plutôt qu'en termes de requêtes, de réponses et de langages à marqueurs.

Son but est de faciliter et d'accélérer le développement des applications web en fournissant des composants graphiques (comme des zones de texte, les listes, les onglets, les grilles, etc.) afin d'adopter une approche RAD (Rapid Application Development).

Choix du chapitre Introduction à JSF

Les applications JSF sont des applications web classiques qui interceptent HTTP via la servlet Faces et produisent du HTML.

En coulisse, cette architecture permet de greffer n'importe quel langage de déclaration de page (PDL), de l'afficher sur des dispositifs différents (navigateur web, terminaux mobiles, etc.) et de créer des pages au moyen d'événements, d'écouteurs et de composants, comme en Swing.

JSF fournit un ensemble de widgets standard (boutons, liens hypertextes, cases à cocher, zone de saisie, tableaux dynamiques, etc.) et facilite son extension par l'ajout de composants tiers.


La figure ci-dessous représente les parties les plus importantes de l'architecture JSF qui la rendent aussi riche et aussi souple :

  1. FacesServlet : est la servlet principale de l'application qui sert de contrôleur. JSF utilise le patron de conception MVC (Modèle-Vue-Contrôleur). MVC permet de découpler la vue (la page) et le modèle (le traitement des données affichées dans la vue). Le contrôleur prend en charge les actions de l'utilisateur qui pourraient impliquer des modifications dans le modèle et dans les vues. Avec JSF, ce contrôleur est la servlet FacesServlet. Toutes les requêtes de l'utilisateur passent sytématiquement par elle, qui les examine et appelle les différentes actions correspondantes du modèle en utilisant les beans gérés. Il est possible de configurer le comportement de cette servlet au travers d'annotations spécifiques (sur les beans gérés, les convertisseurs, les composants, les moteurs de rendu et les validateurs).
  2. Pages et composants : Le framework JSF doit envoyer une page sur le dispositif de sortie du client (un navigateur, pas exemple) et exige donc une technologie d'affichage appelée PDL. Dans sa version la plus récente, JSF utilise plutôt les Facelets. Facelets est formée d'une arborescence de composants (également appelés widgets ou contrôles) fournissant des fonctionnalités spécifiques pour interagir avec l'utilisateur (champ de saisie, boutons, liste, etc.).

    JSF dispose d'un ensemble standard de composants (graphiques ou sans apparences visuelles) et permet de créer également facilement les vôtres (composants personnalisés). Pour gérer cette arborescence, une page passe par un cycle de vie complexe (initialisation, événements, affichage, etc.).

    Le code ci-dessous correspond à la page web de conversion vue plus haut et représente une page Facelets en XHTML qui utilise les marqueurs xmln:h et xmlns:f pour afficher un formulaire avec les deux zones de saisies (€uro et Franc) et un bouton (pour la conversion). Cette page est composée de plusieurs composants JSF : certains n'ont pas d'apparence visuelle, comme ceux qui déclare l'en-tête <h:head>, le corps <h:body> ou le formulaire <h:form>. D'autres ont une représentation graphique et affichent un texte en sortie <h:outputText>, une zone de saisie <h:inputText> ou un bouton <h:commandButton>. Vous remarquez que nous pouvons également utiliser des marqueurs HTML purs : <html>, <h3>, <hr />.


  3. Moteurs de rendu : JSF reconnait deux modèles de programmation pour afficher les composants : l'implémentation directe et l'implémentation déléguée. Avec le modèle direct, les composants doivent eux-même s'encoder vers une représentation graphique et réciproquement. Avec le mode délégué, ces opérations sont confiées à un moteur de rendu, ce qui permet aux composants d'être indépendants de la technologie d'affichage (navigateur, terminal mobile, etc.) et donc d'avoir plusieurs représentations graphiques possibles.

    Un moteur de rendu s'occupe d'afficher un composant et de traduire la saisie d'un utilisateur en valeur de composants. Nous pouvons donc le considérer comme un traducteur placé entre le client et le serveur : il décode la requête de l'uilisateur pour initialiser les valeurs du composant et encode la réponse pour créer une représentation du composant que le client pourra comprendre et afficher.

  4. Convertisseurs et validateurs : Lorsque la page est affichée, l'utilisateur peut s'en servir pour entrer des données. Comme il n'y a pas de contraintes sur les types, un moteur de rendu ne peut pas prévoir l'affichage de l'objet. Voilà pourquoi les convertisseurs existent : ils traduisent un objet (Integer, Date, Enum, Boolean, etc.) en chaîne de caractères afin qu'il puisse s'afficher (protocole HTTP est un protocole uniquement textuel) et, inversement, construisent un objet à partir d'une chaîne qui a été saisie. JSF fournit un ensemble de convertisseurs pour les types classiques dans la paquetage javax.faces.convert, mais vous pouvez développer les vôtres ou ajouter des types provenant de tierces parties.

    Parfois, les données doivent également être validées avant d'être traitées par le back-end : c'est le rôle des validateurs ; nous pouvons ainsi associer un ou plusieurs validateurs à un composant unique afin de garantir que les données saisies sont correctes. JSF fournit quelques validateurs (LengthValidator, RegexValidator, etc.) et vous permet d'en créer d'autres en utilisant vos propres classes annotées. En cas d'erreur de conversion ou de validation, un message est envoyé dans la réponse à afficher.

  5. Beans gérés et navigation : Tous les concepts que nous venons de présenter - qu'est-ce qu'une page, qu'est-ce qu'un composant, comment sont-il affichés convertis et validés - sont liés à une page unique, mais les applications web sont généralement formées de plusieurs pages et doivent réaliser un traitement métier (en appelant une couche EJB, par exemple). Le passage d'une page à une autre, l'invocation d'EJB et la synchronisation des données avec les composants sont pris en charge par les beans gérés.

L'exemple ci-dessous visualise un projet d'entreprise de gestion complète d'une bibliothèque. Nous découvrons ainsi toute la partie métier au travers d'un certain nombre d'EJB session, comme GérerAuteurs, GérerLivres, etc. qui s'occupent également de la persistance à l'aide d'entités adaptées. La couche présentation est traitée au moyen de JSF où nous découvrons toute la structure interne. Tout d'abord la servlet FacesServlet qui joue le rôle de contrôleur et qui prend en compte toute les requêtes venant du client déporté. Ensuite, la partie visuelle est représentée par les différentes pages web comme auteurs.xhtml, livres.xhtml, etc. Enfin, ce qui permet de mettre en relation la logique métier avec cette partie visuelle, dans ce que nous appelons le back-end, nous trouvons les beans gérés comme GestionAuteurs, GestionLivres, etc.

Vous remarquez qu'un bean géré est systématiquement associé à une page xhtml. Ce n'est pas absolument indispensable. Nous pouvons tout aussi bien avoir plusieurs beans pour une même page et inversement, un seul bean géré peut s'occuper de plusieurs pages.

  1. Un bean géré est classe Java spécialisée qui synchronise les valeurs avec les composants, traite la logique métier et gère la navigation entre les pages. Nous associons un composant à une propriété ou à une action spécifique d'un bean géré en utilisant EL (Expression Language) :

    Ainsi, par exemple, la première zone de saisie lie directement la valeur du champ à la propriété euro. Cette valeur est alors automatiquement synchronisée avec la propriété du bean géré.

    Un bean géré peut également traiter des événements et associer un bouton de soumission à une action spécifique. Ainsi, lorsque nous cliquons sur la bouton de conversion, celui-ci déclenchera un événement sur le bean géré, qui exécutera alors une méthode écouteur - ici, euroFranc().

    Le bean géré est une classe Java annotée par @ManagedBean et possède deux propriétés, euro et franc, qui sont synchronisées avec les valeurs des composants de la page. La méthode euroFranc() de ce bean managé réalise le traitement de la conversion.

  2. Support d'Ajax : Une application web doit fournir une interface riche et rapide. Cette réactivité peut être obtenue en ne modifiant que de petites parties de la page de façon asynchrone, sans que la page ne soit rechargée entièrement, et c'est exactement pour cela qu'Ajax a été conçu.

    Par exemple, sur notre application web de conversion, nous pourrions souhaiter que seule la zone des francs soit changée lors de la soumission de la requête de conversion à partir des €uros :

    Voici le code correspondant :

Cycle de vie

Une page JSF est une arborescence de composants avec un cycle de vie spécifique qu'il faut bien avoir compris pour savoir à quel moment les composants sont validés ou quand le modèle est mis à jour.

  1. Un clic sur un bouton provoque l'envoi d'une requête du navigateur vers le serveur et cette requête est traduite en événement qui peut être traité par l'application sur le serveur.
  2. Toutes les données saisie par l'utilisateur passent par une étape de validation avant que le modèle soit mis à jour et que du code métier soit appelé.
  3. JSF se charge alors de vérifier que chaque composant graphique (composants parent et fils) est correctement rendu par le navigateur.
Le cycle de vie de JSF se divise en six phases :

  1. Restauration de la vue : JSF trouve la vue cible et lui applique les entrées de l'utilisateur. S'il s'agit de la première visite, JSF crée la vue comme un composant UIViewRoot (racine de l'arborescence de composants, qui constitue une page particulière). Pour les requêtes suivantes, il récupère l'UIViewRoot précédemment sauvegardée pour traiter la requête HTTP courante.
  2. Application des valeurs de la requête : Les valeurs fournies avec la requête (champ de saisie, d'un formulaire, valeurs des cookies ou à partir des en-têtes HTTP) sont appliquées aux différents composants de la page. Seuls les composants UI (de la page) modifient leur état, non les objets métiers qui forment le modèle.
  3. Validations : Lorsque tous les composants UI ont reçu leurs valeurs, JSF traverse l'arborescence de composants et demande à chacun d'eux de s'assurer que la valeur qui leur a été soumise est correcte. Si la conversion et la validation réussissent pour tous les composants, le cycle de vie passe à la phase suivante. Sinon il passe à la phase de Rendu de la réponse avec les messages d'erreur de validation et de conversion appropriés.
  4. Modification des valeurs du modèle : Lorsque toutes les valeurs des composants ont été affectées et validées, les beans gérés qui leur sont associés peuvent être mis à jour.
  5. Appel de l'application : Nous pouvons maintenant exécuter la logique métier. Les actions qui ont été déclenchées seront exécutées sur le bean géré. La navigation entre en jeu car c'est la valeur qu'elle renvoie qui déterminera la réponse.
  6. Rendu de la réponse : Le but principal de cette phase consiste à renvoyer la réponse à l'utilisateur. Son but secondaire est de sauvegarder l'état de la vue pour pouvoir la restaurer dans la phase de restauration si l'utilisateur redemande la vue.

Le thread d'exécution d'un cycle requête/réponse peut passer ou non par chacune de ces étapes en fonction de la requête et de ce qui se passe au cours de son traitement : en cas d'erreur notamment, le flux d'exécution passe directement à la phase de Rendu de la réponse. Quatre de ces étapes peuvent produire des messages d'erreur : Application des valeurs de la requête (2), Validation (3), Modification des valeurs du modèle (4) et Appel de l'application (5). Avec ou sans erreur, la phase de Rendu de la réponse (6) renvoie toujours le résultat à l'utilisateur.

Petit résumé

Nous venons de le voir, créer des pages contenant des composants graphiques ne suffit pas : ces pages doivent interagir avec un back-end (un processus en arrière plan), il faut pouvoir naviguer entre les pages et valider et convertir les données. JSF est une spécification très riche : les beans gérés permettent d'invoquer la couche métier, de naviguer dans votre application, et, grâce à un ensemble de classes, vous pouvez convertir les valeurs des composants ou les valider pour qu'ils correspondent aux règles métiers. Grâces aux annotations, le développement de convertisseurs et de validateurs personnalisés est désormais chose facile.

JSF 2.0 apporte la simplicité et la richesse aux interfaces utilisateurs dynamiques. Il reconnait nativement Ajax en fournissant une bibliothèque JavaSript permettant d'effectuer des appels asynchrones vers le serveur et de rafraîchir une page par parties.

La création d'interfaces utilisateurs, le contrôle de la navigation dans les appels synchrones ou asynchrones de la logique métier sont possibles parce que JSF utilise le modèle de conception MVC (Modèle-Vue-Contrôleur). Chaque partie est donc isolée des autres, ce qui permet de modifier l'interface utilisateur sans conséquence sur la logique métier et vice versa.

 

Choix du chapitre Le modèle MVC et architecture JSF

JSF encourage la séparation des problèmes en utilisant une des variantes du modèle MVC. Ce dernier est un modèle d'architecture permettant d'isoler la logique métier de l'interface utilisateur car la première ne se mélange pas bien avec la seconde.

Avec PHP ou JSP, le mélange de la logique métier et de l'interface utilisateur produit des applications plus difficiles à maintenir et qui supportent moins bien la montée en charge. En effet, une page JSP qui contient à la fois du code Java et des instructions SQL au millieu des balises HTML : bien que ce soit techniquement correct, imaginez la difficulté de maintenir une telle page... Elle mélange deux types de développement différents (celui du concepteur graphique et celui de programmeur métier) et pourrait finir par utiliser bien plus d'API encore (accès aux bases de données, appels d'EJB, etc.), par gérer les exceptions ou par effectuer des traitements métiers complexes.

Avec MVC, l'application utilise un couplage faible, ce qui facilite la modification de son aspect visuel ou des règles métiers sous-jacentes sans pour autant affecter l'autre composante. Par exemple, nous avons travailler sur deux vues différentes (conversion.xhtml), avec ou sans prise en compte d'une action Ajax, sans que nous ayons modifier quoi que ce soit sur le bean géré. Revoici d'ailleurs les deux alternatives :


Comme le montre la figure ci-dessous, la partie modèle de MVC représente les données (et leurs différents traitements) de l'applications ; la vue correspond à l'interface utilisateur et le contrôleur gère la communication entre les deux :

  1. Le modèle est représenté par le contenu, qui est quelque fois stocké dans une base de données et affiché dans la vue ; il ne se soucie pas de l'aspect que verra l'utilisateur. Avec JSF, il peut être formé de backing beans, d'appels EJB, d'entités JPA, etc.

  2. La vue JSF est la véritable page XHTML (XHTML est réservée aux interfaces web, mais il pourrait s'agir d'autre type de vue, comme WML pour les dispositifs mobiles). Une vue fournit une représentation graphique d'un modèle et un modèle peut avoir plusieurs vues pour prévoir un affichage sous forme de formulaire ou sous forme de liste, par exemple.

  3. Lorsqu'un utilisateur manipule une vue, celle-ci informe un contrôleur des modifications souhaitées. Ce contrôleur se charge alors de rassembler, convertir et valider les données, appelle la logique métier puis produit le contenu en XHTML. Avec JSF, le contrôleur est un objet FacesServlet.

    Cette servlet est déjà fabriquée. Nous n'avons aucun code à écrire en son sein. Nous pouvons toutefois influencer son comportement au moyen d'annotations spécifiques, comme par exemple @ManagedBean. Dans ce cas là, la servlet s'occupera alors de générer un nouvel objet correspondant à la classe du bean géré avec une durée de vie adaptée.

Architecture JSF au travers de l'exemple complet de conversion

Nous allons valider tous les différents concepts que nous venons de découvrir durant tout le début de cette étude au travers du projet de conversion sur lequel nous avons également travaillé. Nous en profiterons pour découvrir l'architecture d'une application web traité avec JSF et de voir comment se traite la gestion des resources ainsi que le descripteur de déploiement (web.xml).

Nous allons maintenant suivre toute la procédure pour réaliser ce projet au travers de l'environnement Netbeans. Ce projet, je le rappelle, permet de réaliser la conversion monétaire des €uros vers les francs (uniquement dans ce sens).
Mise en place du projet
  1. Création d'un projet de type application Web :

  2. Nom du projet Francs :

  3. Choix du serveur d'applications et des réglages de base :

  4. Prise en compte de la technologie JSF pour cette application web :

Feuille de style CSS et gestion des ressources

La plupart des composants ont besoin de ressources externes pour s'afficher correctement : <h:graphicImage> a besoin d'une image, <h:commandButton> peut également afficher une image pour représenter le bouton, <h:outputScript> référence un fichier JavaScript et surtout les composants peuvent également appliquer des styles CSS (Sujet qui m'intéresse ici).

Avec JSF, une ressource est un élément statique qui peut être transmis aux éléments afin d'être affiché (images) ou traité (CSS) par le navigateur. JSF 2.0 permet d'assembler directement les ressources dans un fichier jar séparé, avec un numéro de version et une locale, et de les placer à la racine de l'application web, sous le répertoire suivant :
resources/<identifiant_ressource>
ou
META-INF/resources/<identifiant_ressource>

<identifiant_ressource> est formé de plusieurs sous-répertoire indiqués sous la forme :

[locale/ ] [nomBibliothèque/ ] nomRessource [ /versionRessource]

Tous les éléments entre crochets sont facultatifs. La locale est le code du langage, suivi éventuellement d'un code de pays (en, en_US, pt, pt_BR). Comme l'indique cette syntaxe, vous pouvez ajouter un numéro de version à la bibliothèque ou à la ressource elle-même.

Voici quelques exemples :

resources/principal.css
resources/css/principal.css

Après toutes ces considérations techniques, nous allons maintenant élaborer l'ensemble de nos styles de la page dans une feuille séparée. La suite nous montre la procédure à suivre :

  1. Netbeans permet de prendre en compte les feuilles de styles :

  2. Le plus important et de bien choisir l'emplacement, avec bien sûr le nom de votre feuille de style :

  3. Voici le résultat avec la prise en compte de l'emplacement :

principal.css
root { 
    display: block;
}

body {
    background-color: orange;
    color: maroon;   
    font-family: Verdana,Arial,Helvetica,sans-serif;
    text-align: center;
}

.saisie, .resultat {
    background-color: yellow;
    text-align: right;
    font-weight: bold;
    color: green;
}

.resultat {
    color: red;
}  

.erreur {
    color: black;
}  

Le fait de choisir une feuille de style plutôt que d'intégrer les styles directement dans la vue me paraît être une attitude de bon développeur. Il faut toujours séparer la mise en forme de votre page web par rapport à son ossature, surtout si vous devez en construire plusieurs. Toutes les pages respecterons ainsi la même charte graphique. A tout moment, vous pouvez changer cette charte sans remettre en cause les structures de développement de vos différentes vues. Pour finir, lorsque vous prenez une feuille de style à part entière, vous bénéficiez d'un éditeur CSS intégré, ce qui n'est pas négligeable.

Le contrôleur FacesServlet et le descripteur de déploiement web.xml

Lorsqu'un utilisateur manipule une vue, celle-ci informe un contrôleur des modifications souhaitées. Ce contrôleur se charge alors de rassembler, convertir et valider les données, appelle la logique métier puis produit le contenu en XHTML. Avec JSF, le contrôleur est un objet FacesServlet.

FacesServlet est une implémentation de javax.servlet.Servlet qui sert de contrôleur central par lequel passent toutes les requêtes. Comme le montre la figure ci-dessous, la survenue d'un événement, lorsque l'utilisateur clique sur un bouton, par exemple, provoque l'envoi d'une notification au serveur via HTTP ; celle-ci est interceptée par javax.faces.webapp.FacesServlet, qui examine la requête et exécute différentes actions sur le modèle à l'aide des beans gérés.

En coulissse, la FacesServlet prend les requêtes entrantes et donne le contrôle à l'objet javax.faces.lifecycle.LifeCycle. A l'aide d'une méthode de fabrique, elle crée un objet javax.faces.contextFacesContext qui contient et traite les informations d'état de chaque requête. L'objet Lifecycle utilise ce FacesContext en six étapes (décrites au chapitre précédent) avant de produire la réponse.

Les requêtes qui doivent être traitées par la FacesServlet sont redirigées à l'aide d'une association de servlet dans le descripteur de déploiement (web.xml) qui se situe dans le répertoire WEB-INF. Les pages web, les beans gérés, les convertisseurs, etc. doivent être assemblés à l'aide de ce fichier.

  1. Ce fichier définit la javax.faces.webapp.FacesServlet en lui donnant un nom (Faces Servlet, ici) et une association. Dans cet exemple, toutes les requêtes dont le préfixe est faces/ sont associées pour être gérées par la servlet - toute requête de la forme http://localhost:8080/Francs/faces/*.xhtml sera donc traité par JSF.
  2. Nous pouvons en profiter pour définir la page d'accueil à l'aide du marqueur <welcome-file>, ici conversion.xhtml. Cette balise est optionnelle. Si vous ne voulez pas la définir, la page d'accueil doit alors s'appeler index.xhtml.
  3. En option, vous pouvez également configurer quelques paramètres spécifiques à JSF dans l'élément <context-param>. dont la liste est proposée ci-dessous :
    Paramètre Description
    javax.faces.CONFIG_FILES Définit une liste de chemins de ressources liées au contexte dans lequel JSF recherchera les ressources.
    javax.faces.DEFAULT_SUFFIX Permet de féfinir une liste de suffixes possibles pour les pages ayant du contenu JSF (.xhtml, par exemple).
    javax.faces.LIFECYCLE_ID Identifie l'instance LifeCycle utilisée pour traiter les requêtes JSF.
    javax.faces.STATE_SAVING_METHOD Définit l'emplacement de sauvegarde de l'état. Les valeurs possibles sont server (valeur par défaut qui indique que l'état sera généralement sauvegardé dans un objet HttpSession) et client (l'état sera sauvegardé dans champ caché lors du prochain envoi de formulaire).
    javax.faces.PROJECT_STAGE Décrit l'étape dans laquelle se trouve cette application JSF dans le cycle de vie (Development, UnitTest, SystemTest ou Production). Cette information peut être utilisée par une implémentation de JSF pour améliorer les performances lors de la phase de production en utilisant un cache pour les ressources, par exemple.
    javax.faces.DISABLE_FACELET_JSF_VIEWHANDLER Désactive Facelets comme langage de déclaration de page (PDL).
    javax.faces.LIBRARIES Liste des chemins qui seront considérés comme une bibliothèque de marqueurs Facelets.
Le bean géré Conversion

Comme nous l'avons évoqué plus haut dans ce chapitre, le modèle MVC encourage la séparation entre le modèle, la vue et le contrôleur. Avec Java EE, les pages JSF forment la vue et la FacesServlet est le contrôleur. Les beans gérés, quant à eux, sont une passerelle vers le modèle.

  1. Les beans gérés sont des classes Java annotées. Ils constituent le coeur des applications web car ils exécutent la logique métier (ou la délèguent aux EJB, par exemple), gèrent la navigation entre les pages et stockent les données. Une application JSF typique contient un ou plusieurs beans gérés qui peuvent être partagés par plusieurs pages.
  2. Les données sont stockées dans les attributs du bean géré, qui, en ce cas, est également appelé "backing bean". Un backing bean définit les données auxquelles est lié un composant de l'interface utilisateur (la cible d'un formulaire, par exemple). Pour établir cette liaison, nous utilisons EL, le langage d'expressions.
  3. Ecrire un bean géré est aussi simple qu'écrire un EJB ou une entité JPA puisqu'il s'agit simplement de créer une classe Java annotée par @ManagedBean (ou @Named, nous verrons la différence entre les deux écritures lors du prochain chapitre).
  4. Les beans gérés sont des classes Java prises en charge par la FacesServlet. Les composants de l'interface utilisateur sont liés aux propriétés du bean (backing bean) et peuvent invoquer des méthodes d'action.
  5. Bien qu'un bean géré puisse être une simple classe annotée, sa configuration peut être personnalisée grâce aux éléments de @ManagedBean et @ManagedProperty (voir pour plus de détail dans le chapitre suivant).
  6. La présence de l'annotation @javax.faces.model.ManagedBean sur une classe l'enregistre automatiquement comme un bean géré. C'est la FacesServlet qui gère cette classe. Par défaut, le nom du bean géré (objet) est celui de la classe commençant par une minuscule. Il est possible de choisir un autre nom en spécifiant l'attribut name de l'annotation @ManagedBean.
  7. Les composants de l'interface utilisateur étant liés aux propriétés du bean géré, changer son nom par défaut a des répercussions sur la façon d'appeler une propriété ou une méthode.
  8. Les objets créés dans le cadre d'un bean géré ont une certaine durée de vie et peuvent ou non être accessibles aux composants de l'interface utilisateur ou aux objets de l'application. Cette durée de vie et cette accessibilté sont regroupées dans la notion de portée. Plusieurs annotations (que nous étudierons plus en détail dans la chapitre suivant) permettent de définir la portée d'un bean géré.
  9. La portée que je choisi pour le bean géré est @ViewScoped. Cet objet est alors disponible dans une vue donnée jusqu'à sa modification. Son état persiste jusqu'à ce que l'utilisateur navigue vers une autre vue, auquel cas il est supprimé.

Après toutes ces réflexions, je vous propose maintenant de créer notre bean géré Conversion à l'aide de notre outil de développement Netbeans. Voici la procédure à suivre :

  1. Création d'un nouveau bean géré :

  2. Demande de création d'un bean géré dont le nom de l'objet sera conv issu de la classe Conversion et dont la durée de vie correspond exactement à la durée de vie de la page à afficher :

  3. Nous plaçons toute la logique métier dans ce bean géré, et c'est lui qui réalise la conversion nécessaire. Voir le code ci-dessous.
bean.Conversion.java
package bean;

import javax.faces.bean.*;

@ManagedBean(name="conv")
@ViewScoped
public class Conversion {
    private final double TAUX = 6.55957;
    private double euro;
    private double franc;

    public double getEuro() { return euro;  }
    public void setEuro(double euro) { this.euro = euro; }

    public double getFranc() { return franc;  }
    public void setFranc(double franc) { this.franc = euro * TAUX;  }
    
    public void euroFranc() { franc = euro * TAUX;  }
}
  1. Mise à part les annotations, nous retrouvons une classe normale avec des propriétés et une méthode de traitement.
  2. Ces propriétés sont indispensables pour que la page web puisse y accéder et qu'il y ait une interaction possible entre le modèle et la vue.
  3. L'annotation @ManagedBean est également indispensable pour stipuler que cette classe doit être prise en compte par le contrôleur (être gérée). C'est finalement l'équivalent d'un dispositif de configuration puisque nous demandons ainsi que le nom de l'objet correspondant s'appelle conv. Par ailleurs, nous configurons également la durée de vie de cet objet pour qu'il soit en adéquation avec la vue au moyen de l'annotation @ViewScoped.
  4. Grâce à ces simples annotations, il est très facile de changer de nom d'objet ainsi que sa durée de vie.
La vue conversion.xhtml

Le modèle, vous venez de le découvrir est très simple. Nous nous intéressons maintenant à notre dernier élément de l'application wen, à savoir la vue, plus précisément la page conversion.xhtml.

Lorsque vous travaillez avec la vue, vous disposez d'un certain nombre de composants (marqueurs) déjà construits qui sont installés dans des bibliothèques spécifiques qu'il faut prendre en compte lors de l'élaboration de vos pages web si vous désirez les utiliser. Voici le tableau qui recense les bibliothèques préfabriquées :

URI Préfixe classique Description
http://java.sun.com/jsf/html h Contient les composants et leurs rendus HTML (h:commandButton, h:inputText, etc.)
http://java.sun.com/jsf/core f Contient les actions personnalisées indépendantes d'un rendu particulier (f:convertNumber, f:validateDoubleRange, f:param, etc.)
http://java.sun.com/jsf/facelets ui Marqueurs pour le support les modèles de page.
http://java.sun.com/jsf/composite composite Sert à déclarer et à définir des nouveaux composants personnalisés.
http://java.sun.com/jsp/jstl/core c Les pages Facelets peuvent éventuellement utiliser certains marqueurs issus de JSP (c:if, c:forEach et c:catch). A éviter si possible.
http://java.sun.com/jsp/jstl/functions fn Les pages Facelets peuvent utiliser tous les marqueurs de fonctions issus de la technologie JSP.
conversion.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title><h:outputText value="Conversion entre les €uros et les francs" /></title>
    </h:head>
    
    <h:outputStylesheet library="css" name="principal.css" />
    
    <h:body>
        <h3><h:outputText value="Conversion entre les €uros et les francs" /></h3>
        <hr />
        <h:form>            
            <h:inputText value="#{conv.euro}" 
                                  styleClass="saisie" 
                                  id="euro"
                                  converterMessage="Il faut une valeur monétaire"
                                  validatorMessage="Uniquement les nombres positifs"
                                  required="true"
                                  requiredMessage="Précisez votre valeur monétaire">
                
                <f:convertNumber type="currency" currencySymbol="€"/>
                <f:validateDoubleRange minimum="0.0" />
            </h:inputText>
            
            <h:commandButton value="Conversion" action="#{conv.euroFranc}" />
            
            <h:inputText value="#{conv.franc}" readonly="true" styleClass="resultat">
                <f:convertNumber type="currency" currencySymbol="F"/>
            </h:inputText>  
            
            <p><h:message for="euro" styleClass="erreur"/></p>
        </h:form>  
    </h:body>
</html>
  1. Au début, dans la balise <html>, vous spécifiez les bibliothèques qui vous serons utiles pour la constitution de votre page.
  2. Par exemple, sauf cas très rare, nous avons toujours besoin des marqueurs relatifs au rendu, comme les zones de saisie, les boutons etc. Aussi, nous proposons l'espace de nom h (pour HTML) associé à la bibliothèque xmlns:h="http://java.sun.com/jsf/html".
  3. Pour les marqueurs qui réalise du traitement en coulisse, comme les convertisseurs et les validateurs, nous introduisons la bibliothèque xmlns:f="http://java.sun.com/jsf/core". (Nous aurions pu prendre l'espace de nom c, mais il est généralement déjà utilisé par la JSTL). Quelquefois, je prend comme espace de nom core.
  4. La balise <h:outputStylesheet library="css" name="principal.css" /> nous permet de prendre en compte la feuille de style principal.css que nous avons mise en oeuvre au préalable, dans la bibliothèque (répertoire) css. Vous pouvez placer votre feuille de style directement dans le répertoire resources (donc sans bibliothèque), auquel cas vous n'avez plus à spécifier l'attribut library.
  5. Nous retrouvons pratiquement le même code que nous avons déjà consulté mainte fois. Vous remarquez toutefois la présence d'un nouveau marqueur <h:message for="euro" styleClass="erreur"/> qui permet d'avertir l'utilisateur d'une mauvaise utilisation sur la zone de saisie des €uros.
  6. Cette balise doit toujours être rattachée à une autre au moyen de l'attribut for. De la même façon, la balise en question, comme ici <h:inputText value="#{conv.euro}" id="euro">, doit posséder l'attribut id à l'intérieur duquel vous spécifiez un nom de variable à prendre en compte.
  7. Toujours dans cette balise, vous avez la possibilité de spécifier les messages qui s'afficheront automatiquement lors d'une erreur de saisie particulière. Ici, nous proposons des messages d'erreur de conversion, de validation et de champ non vide, respectivement avec les atttributs converterMessage, validatorMessage et requiredMessage.
  8. Voici d'ailleurs les différents résultats obtenus suivant les cas d'utilisation :

N'oubliez pas que nous ici dans la technique des pages web dynamiques, c'est-à-dire que tout les marqueurs que nous proposons ne sont pas envoyés au client. La page web conversion.xhtml reste sur le serveur et le système JSF envoie une page web au format HTML classique suivant la requête proposée par le client (pas besoin d'une installation Java spécifique). Voici justement le code source de la page web qui est envoyée par rapport aux valeurs proposées ci-dessus :

Conclusion

 

Choix du chapitre Les beans gérés et la navigation

Comme nous l'avons évoqué plus haut dans ce chapitre, le modèle MVC encourage la séparation entre le modèle, la vue et le contrôleur. Avec Java EE, les pages JSF forment la vue et la FacesServlet est le contrôleur. Les beans gérés, quant à eux, sont une passerelle vers le modèle. Je rappelle les points importants que nous avons déjà évoqué dans le chapitre précédent :

  1. Les beans gérés sont des classes Java annotées. Ils constituent le coeur des applications web car ils exécutent la logique métier (ou la délèguent aux EJB, par exemple), gèrent la navigation entre les pages et stockent les données. Une application JSF typique contient un ou plusieurs beans gérés qui peuvent être partagés par plusieurs pages.
  2. Les données sont stockées dans les attributs du bean géré, qui, en ce cas, est également appelé "backing bean". Un backing bean définit les données auxquelles est lié un composant de l'interface utilisateur (la cible d'un formulaire, par exemple). Pour établir cette liaison, nous utilisons EL, le langage d'expressions.
  3. Ecrire un bean géré est aussi simple qu'écrire un EJB ou une entité JPA puisqu'il s'agit simplement de créer une classe Java annotée par @ManagedBean ou @Named.
  4. Les beans gérés sont des classes Java prises en charge par la FacesServlet. Les composants de l'interface utilisateur sont liés aux propriétés du bean (backing bean) et peuvent invoquer des méthodes d'action.
  5. Bien qu'un bean géré puisse être une simple classe annotée, sa configuration peut être personnalisée grâce aux éléments de @ManagedBean et @ManagedProperty.
  6. La présence de l'annotation @javax.faces.model.ManagedBean ou @javax.inject.Named sur une classe l'enregistre automatiquement comme un bean géré. C'est la FacesServlet qui gère cette classe. Par défaut, le nom du bean géré (objet) est celui de la classe commençant par une minuscule. Il est possible de choisir un autre nom en spécifiant l'attribut name de l'annotation @ManagedBean ou de l'annotation @Named.
  7. Les composants de l'interface utilisateur étant liés aux propriétés du bean géré, changer son nom par défaut a des répercussions sur la façon d'appeler une propriété ou une méthode.

Pour conclure sur cette première approche, un bean géré doit respecter les contraintes suivantes :

  1. Le classe doit être annotée par @ManagedBean ou @Named.
  2. La classe doit avoir une portée (qui vaut par défaut @RequestScoped). (Nous découvrirons les différentes portées dans la suite du chapitre).
  3. La classe doit être publique et non finale ni abstraite.
  4. La classe doit fournir un constructeur public sans paramètre qui sera utilisé par le conteneur pour créer les instances.
  5. La classe ne doit pas définir de méthode finalize().
  6. Pour être liés à un composant graphique de la vue, les attributs doivent avoir des getters et des setters public (ce que nous appelons des propriétés). En réalité, le point de contact d'un composant graphique avec la propriété se fait toujours au travers des méthodes. Du coup, dans certains cas, il n'est pas toujours nécessaire d'avoir un attribut qui conserve la valeur. Par contre, la syntaxe du getter et du setter doit être respectée.

Les beans CDI

Jusqu'à présent, nous avons surtout travaillé sur les beans managés au travers de l'annotation @ManagedBean. Cependant, les beans managés sont assez limités. JSF, avec sa dernière version, définie un modèle de bean géré plus flexible, nommé CDI (Contexts and Dependency Injection). Ce bean, comme son nom l'indique, est lié à un context (comme, par exemple, la requête en cours, la session actuelle du navigateur, ou même le cycle de vie du context de l'utilisateur.

CDI spécifie un mécanisme pour injecter des beans gérés, pour intercepter les méthodes d'appel, et prendre en compte à la volée les événements liées aux différentes requêtes du client web.

Ceci dit, nous pouvons continuer à prendre des beans managés. Dans bien des cas ils sont généralement suffisants, notamment si nous n'avons pas besoin d'interaction. Ils permettent d'avoir une gestion autonome du modèle.

Là où les beans CDI sont importants, c'est justement lorsque nous désirons avoir une forte interaction avec les autres éléments du serveur d'applications, comme les beans sessions, les entités, les applications web de type Rest. Ainsi, par exemple, l'injection permet d'avoir un bean session qui sert également de bean géré en relation directe avec la vue, à condition toutefois que ce bean session soit de type stateful afin de permettre le suivi des différentes requêtes de l'application web.

Après toutes ces définissions, venons en maintenant à l'écriture proprement dite. Nous retrouvons pratiquement la même syntaxe que les beans managés. Vous avez juste à utiliser l'annotation @Named en lieu et place de l'annotation @ManagedBean, comme ci-dessous :
bean.Conversion.java
package bean;

import javax.io.Serializable;
import javax.inject.Named;                                    // importation différente
import javax.enterprise.context.SessionScoped;    // importation différente

@Named("conv")
@SessionScoped      // la durée de vie Vue n'existe pas dans les beans CDI
public class Conversion implementation Serializable { // Nous devons également faire en sorte que l'objet soit sérialisé
    private final double TAUX = 6.55957;
    private double euro;
    private double franc;

    public double getEuro() { return euro;  }
    public void setEuro(double euro) { this.euro = euro; }

    public double getFranc() { return franc;  }
    public void setFranc(double franc) { this.franc = euro * TAUX;  }
    
    public void euroFranc() { franc = euro * TAUX;  }
}
  1. Mise à part quelques petits détails, le code du bean géré correspondant à la conversion demeure similaire.
  2. Notamment nous pouvons conserver la même vue que précédemment. Ainsi, l'accès à une propriété de ce bean se fait exactement de la même façon : #{conv.euro}, par exemple.
  3. Les différences notables concerne les importations d'une part, et surtout, notre bean doit être sérialisé.
  4. Il existe des durées de vie différentes dans les deux types de bean.
  5. La mise en oeuvre des beans CDI est également différente avec Netbeans. Nous verrons comment procéder lors du prochain exemple.

Portée et durée de vie des beans gérés

Les objets créés dans le cadre d'un bean géré ont une certaine durée de vie et peuvent ou non être accessibles aux composants de l'interface utilisateur ou aux objets de l'application. Cette durée de vie et cette accessibilté sont regroupées dans la notion de portée. Plusieurs annotations permettent de définir la portée d'un bean géré.

Lorsque vous définissez un bean géré, vous êtes obligés de spécifier également sa portée. Trois portées sont communes au beans managés et beans CDI : les portées de type Session, de type Requête et de type Application.

JSF 2.0 rajoute les portées de type Vue et de type Personnalisée qui ne sont pas supportées par les beans CDI mais qui proposent en contre partie une portée plus puissante qui s'appelle Conversation.

Avec JSF 2.0, vous pouvez utiliser les annotations suivantes pour prendre en compte les portées communes : @SessionScoped, @RequestScoped et @ApplicationScoped. Notez que ces annotations sont décrites dans le paquetage javax.faces.bean pour les beans managés et dans le paquetage javax.enterprise.context pour les beans CDI.

Définition de chacune des portées
  1. @ApplicationScoped : Il s'agit de l'annotation la moins restrictive, avec la plus longue durée de vie. Les objets créés sont disponibles dans tous les cycles requête/réponse de tous les clients utilisant l'application tant que celle-ci est active. Ces objets peuvent être appelés de façon concurrente et doivent dont être thread-safe (c'est-à-dire utiliser le mot-clé synchronized). Les objets ayant cette portée peuvent utiliser d'autres objets sans portée ou avec une portée d'application.

    Ce type de bean est construit dès l'apparition de la première requête d'un client et reste actif jusqu'à ce que l'application web soit enlevée du serveur d'applications. Cependant, il est possible de faire en sorte que ce bean soit actif plus tôt, bien avant que la première page soit affichée, dès que l'application web est déployée sur le serveur, au moyen de l'attribut eager, comme suit :

    @ManagedBean(eager=true)

  2. @SessionScoped : Ces objets sont disponibles pour tous les cycles requête/réponse de la session du client. Leur état persiste entre les requêtes et dure jusqu'à la fin de la session. Ils peuvent utiliser d'autres objets sans portée, avec une portée de session ou d'application.

    Rappelez-vous que le protocole HTTP est sans état. Le navigateur envoie une requête au serveur, le serveur renvoie la réponse, et ni le navigateur ni le serveur sont dans l'obligation, dans ce protocole, de garder en mémoire la transaction.

  3. @ViewScoped : (Uniquement pour les beans managés) : Ces objets sont disponibles dans une vue donnée jusqu'à sa modification. Leur état persiste jusqu'à ce que l'utilisateur navigue vers une autre vue, auquel cas il est supprimé. Ils peuvent utiliser d'autres objets sans portée, avec une portée de vue, de session ou d'application.
  4. @RequestScoped : Il s'agit de la portée par défaut. Ces objets sont disponibles du début d'une requête jusqu'au moment où la réponse est envoyée au client. Un client pouvant exécuter plusieurs requêtes tout en restant sur la même vue, la durée @ViewScoped est supérieure à celle de @RequestScoped. Les objets ayant cette portée peuvent utiliser d'autres objets sans portée, avec une portée de requête, de vue, de session ou d'application.

    Avec cette portée, la durée de vie est très courte. La création du bean se réalise dès qu'une requête est proposée et se détruit rapidement dès que la réponse à été renvoyée au client. Cela veut dire qu'une nouvelle instance est créée à chaque requête du client. Du coup, pour une même vue, beaucoup de mémoires risquent d'être utilisées pour rien.

  5. @NoneScoped : Les beans gérés ayant cette portée ne sont visibles dans aucune page JSF; ils définissent des objets utilisés par d'autres beans gérés de l'application. Ils peuvent utiliser d'autres objets avec la même portée.
  6. @ConversationScoped : (Uniquement pour les beans CDI) : Cette portée est intéressante lorsque vous avez besoin de gérer plusieurs onglets pour une même session. C'est un petit peu l'intermédiaire entre la portée de type requête et la portée de type session.

La portée des beans gérés doit être judicieusement choisie : vous ne devez leur donner que la portée dont ils ont besoin. Des beans ayant une portée trop grande (@ApplicationScoped, par exemple) augmentent l'utilisation mémoire et l'utilisation du disque pour leur persistance éventuelle. Il n'y a aucune raison de donner une portée d'application à un objet qui n'est utilisé que dans un seul composant. Inversement, un objet ayant une portée trop restreinte (@RequestScoped, par exemple) ne sera pas disponible dans certaines parties de l'application, et éventuellement beaucoup d'objets (avec donc beaucoup de mémoires) risquent d'être créés pour rien.

Injection de valeurs dans les beans (@ManagedProperty et @Inject)

Dans un bean géré, vous pouvez demander au système d'injecter une valeur dans une propriété (un attribut avec un getter et un setter) en utilisant l'annotation @javax.faces.model.ManagedProperty (pour les beans managés) ou @javax.inject.Inject (pour les beans CDI), dont l'attribut value peut recevoir une chaîne ou une expression EL.

Annotation du cycle de vie et des méthodes de rappel

L'introduction nous a montré le cycle de vie d'une page (qui compte six phases, de la réception de la requête à la production de la réponse), mais le cycle de vie des beans gérés est totalement différent : en fait il est totalement identique à celui des beans sessions sans état.

Les beans gérés qui s'exécutent dans un conteneur de servlet peuvent utiliser les annotations @PostConstruct et @PreDestroy.

Ainsi, après avoir créé une instance de bean géré, le conteneur appelle la méthode de rappel @PostConstruct s'il y en a une. Puis le bean est lié à une portée et répond à toutes les requêtes de tous les utilisateurs. Toutefois, avant de supprimer le bean, le conteneur appelle la méthode @PreDestroy. Ces méthodes permettent ainsi d'initialiser les attributs ou de créer et libérer les ressources externes.

bean.GestionLivre.java
@ManagedBean ou @Named
public class GestionLivre  { 
...
    @PostConstruct
    public void début() { ... }
...
    @PreDestroy
    public void fin() { ... }
} 

Langage d'expressions EL

Les instructions EL fournissent une syntaxe simple d'utilisation afin de se connecter à une propriété ou à proposer une action spécifique sur un bean géré. Elles permettent facilement d'afficher les valeurs des variables ou d'accéder aux attributs des objets, aux travers de leurs propriétés, et disposent également d'un grand nombre d'opérateurs mathématiques, logiques et relationnels.

La syntaxe de base d'une instruction EL est de la forme :
#{expression}

Les expressions EL peuvent utiliser la plupart des opérateurs Java habituels :

  1. Arithmétiques : + - * / (division) % (modulo)
  2. Relationnels : == (égalité) != (inégalité) < (inférieur) > (supérieur) <= (inférieur ou égal) >= (supérieur ou égal)
  3. Logiques : && (et) || (ou) ! (non)
  4. Autre : empty (vide) () (parenthèses) [ ] (crochets) . (séparateur) ?: (if arithmétique)

L'opérateur empty teste si un objet est null ou si les collections (tableaux, List, Map, etc. ) sont vides. Nous pouvons ainsi contrôler, par exemple, qu'un livre est défini ou pas :

#{empty livre}

L'opérateur point permet d'accéder à un attribut d'un objet. Une autre syntaxe possible consiste à utiliser l'opérateur [ ]. Nous pouvons ainsi accéder à l'attribut titre de l'objet livre de la façon suivante :

#{livre.titre} ou #{livre[titre]}

Appel de méthodes

Il est également possible d'appeler des méthodes. Si vous désirez par exemple acheter un livre en appelant la méthode acheter() de l'objet livre :

#{livre.acheter}

Depuis la toute dernière version de JSF, nous pouvons également proposer des paramètres aux appels de méthode :

<h:commandButton value="Suivant" action="#{unBean.naviguer(1)}" />
<h:commandButton value="Précédent" action="#{unBean.naviguer(-1)}" />

A condition que cette méthode soit effectivement déclarée avec le bon paramètre :

@ManagedBean
public class UnBean { 
...
    public void naviguer(int sens) { ... }
}

Actions fondamentales

Les actions fondamentales, énumérées dans le tableau ci-dessous, fournissent des marqueurs pour manipuler les beans gérés, des variables, traiter des erreurs, effectuer des tests et exécuter des boucles et des itérations :

Action Description
<c:remove> Supprime une variable.
<c:catch> Capture une exception java.lang.Throuwable lancée par l'une de ses actions imbriquées.
<c:if> Teste si une expression est vrai (la plupart du temps, il est préférable d'utiliser plutôt l'attribut rendered présent dans toutes les balises de JSF).
<c:choose> Fournit plusieurs alternatives exclusives.
<c:when> Représente une alternative dans une section <c:choose>.
<c:otherwise> Représente la dernière alternative d'une action <c:choose>.
<c:forEach> Répète son corps pour chaque élément d'une collection ou un nombre fixé de fois.

Objets prédéfinies

Il existe des identificateurs spéciaux, que nous pouvons atteindre directement dans les différentes vues, qui correspondent à des objets spécifiques déjà prédéfinis. Ce sont des objets implicites parce qu'une page peut les utiliser sans avoir besoin de les déclarer ou de les initialiser explicitement. Ces objets (énumérés dans le tableau ci-dessous) sont utilisable à l'aide des expressions EL.

Objet implicite Description Type renvoyé
application Représente l'environnement de l'application web. Sert à obtenir les paramètres de configuration de cette application. Object
applicationScope Associe les noms des attributs de l'application à leurs valeurs. Map
component Désigne le composant courant. UIComponent
cc Désigne le composant composite courant. UIComponent
cookie Désigne un Map contenant les noms des cookies (clés) et des objets Cookie. Map
facesContext Désigne l'instance FacesContext de cette requête. FacesContext
header Fait correspondre chaque nom d'en-tête HTTP à une seule valeur de type String. Map
headerValue Fait correspondre chaque nom d'en-tête HTTP à un tableau String[] contenant toutes les valeurs de cet en-tête. Map
initParam Fait correspondre les noms des paramètres d'initialisation du contexte à leurs valeurs de type String. Map
param Fait correspondre chaque nom de paramètre à une seule valeur de type String. Map
paramValues Fait correspondre chaque nom de paramètre à un tableau String[] contenant toutes les valeurs de ce paramètre. Map
request Représente l'objet requête HTTP. Object
requestScope Fait correspondre les noms des attributs de la requête à leurs valeurs. Map
resource Indique l'objet ressource. Object
session Représente l'objet session HTTP. Object
sessionScope Fait correspondre les noms des attributs de la session à leurs valeurs. Map
view Représente la vue courante. UIViewRoot
viewScope Fait correspondre les noms des attributs de la vue à leurs valeurs. Map
Tous ces objets implicites sont de vrais objets avec des interfaces. Vous pouvez accéder à leurs attributs avec EL :
  1. Ainsi, #{view.locale}, par exemple, permet d'obtenir le pays dans lequel est consulté cette page (en_US, fr_FR, etc.).
  2. Si vous stockez un livre dans la portée de la session, par exemple, vous pouvez l'atteindre avec #{sessionScope.livre}.
  3. Vous pouvez même utiliser un alogorithme plus élaboré pour afficher tous les en-têtes HTTP et leurs valeurs :
    <?xml version='1.0' encoding='UTF-8' ?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:c="http://java.sun.com/jsp/jstl/core">
        
        <h:body style="background-color: yellow; font-weight: bold">
            <h3>
                <h:outputText value="Lecture des en-têtes de la requête HTTP" />
                <h:outputText value=" (En france)"  rendered="#{view.locale == 'fr_FR'}" />
            </h3>
            <hr />
            <c:forEach items="#{headerValues}" var="élément">
                <h:outputText value="#{élément.key}" style="color: mediumslateblue" /> = 
                <c:forEach var="valeur" items="#{élément.value}">
                    <h:outputText value="#{valeur}" escape="false" style="color: brown" />
                    <br />
                </c:forEach>
            </c:forEach>
        </h:body>
    </html>

Navigation

Les applications web sont formées de plusieurs pages entre lesquelles vous devez naviguer. Selon le cas, il peut exister différents niveaux de navigation avec des flux de pages plus ou moins élaborés.

JSF dispose de plusieurs options de navigation et vous permet de contrôler le flux page par page ou pour toute l'application.
Navigation statique

Les composants <h:commandButton> et <h:commandLink> permettent de passer simplement d'une page à une autre en cliquant sur un bouton ou sur un lien sans effectuer aucun traitement intrinsèque. Il suffit d'initialiser leur attribut action avec le nom de la page vers laquelle vous voulez vous rendre. Ici, par exemple, nous sommes dans la page de configuration du jeu qui se nomme justement "configurer.xhtml". Voici le bouton de navigation que nous pourrions avoir dans cette page pour demander à passer à la page "alea.xhtml" correspondant au jeu lui-même :

<h:commandButton value="Commencer le jeu" action="alea.xhtml" />
Navigation dynamique

Cependant, la plupart du temps, ceci ne suffira pas car vous aurez besoin d'accéder à une couche métier ou à une base de données pour récupérer ou traiter des données. En ce cas, vous aurez besoin d'un bean géré. Par exemple, avant de commencer à jouer, nous avons besoin de faire un certain nombre de traitement avant que le jeu soit totalement prêt. Dans cette situation, le flux des pages reste simple, mais ces deux pages ont pourtant besoin d'un bean géré (Nombre) pour traiter à la fois la logique métier ainsi que la navigation. Elles utilisent toujours les composants bouton (ou lien) pour naviguer et interagir avec ce bean.

Il suffit juste de préciser la méthode du bean géré qui va traiter la logique métier (préparer le jeu) et s'occuper ensuite de la navigation. Ici, par exemple, c'est la méthode recommencer() du bean nombre qui est appelée :

<h:commandButton value="Commencer le jeu" action="#{nombre.recommencer}" />

Les composants bouton et lien n'appellent pas directement la page vers laquelle ils doivent se rendre : ils appellent des méthodes du bean géré qui prennent en charge cette navigation et laissent le code décider de la page qui sera chargée ensuite. La navigation utilise un ensemble de règles qui définissent tous les chemins de navigation possibles de l'application. Dans la forme la plus simple de ces règles de navigation, chaque méthode du bean géré définit directement la page vers laquelle elle doit aller.

@Named
@SessionScoped
public class Nombre implements Serializable {
...
   public String recommencer() {
     alea = (int)(Math.random()*valeurMax)+1;
     tentative = 0;
     valeur = 0;
     test = false;
     return "alea.xhtml";
   }
}

Ainsi, quand le <h:commandButton> invoque la méthode recommencer(), celle-ci initialise le jeu, tire le nombre aléatoire et renvoie le nom de la page vers laquelle naviguer ensuite : alea.xhtml. La FacesServlet redirigera alors le flux de page vers la page désirée. Remarquez au passage, le type de retour String de cette méthode recommencer().

La chaîne renvoyée peut prendre plusieurs formes. Ici, nous avons utilisé la plus simple : le nom de la page. L'extension de fichier par défaut étant .xhtml, nous aurions même pu simplifié le code en supprimant l'extension.

@Named
@SessionScoped
public class Nombre implements Serializable {
...
   public String recommencer() {
     alea = (int)(Math.random()*valeurMax)+1;
     tentative = 0;
     valeur = 0;
     test = false;
     return "alea";
   }
}

Les exemples précédentes nous ont montré une navigation simple où une page n'avait qu'une seule règle de navigation et une seule destination. Ce n'est pas un cas si fréquent : les utilisateurs peuvent généralement être redirigés vers des pages différents en fonction de certaines conditions.

Cette navigation, là encore, peut être mise en place dans les beans gérés. Le code suivant utilise une instruction switch pour rediriger l'utilisateur vers trois pages possibles. Si nous renvoyons la valeur null, l'utilisateur reviendra sur la page sur laquelle il se trouve déjà.

   public String uneMéthode() {
       switch (valeur) {
           case 1 : return "page1.xhtml"; break;
           case 2 : return "page2.xhtml"; break;
           case 3 : return "page3.xhtml"; break;
           default : return null; break;
       }
   }
Redirection et méthodes POST et GET du protocole HTTP

Vous pouvez solliciter l'implémentation JSF pour vous rediriger vers une autre vue. JSF envoie alors une redirection HTTP au client. La redirection est envoyée au navigateur client avec l'URL correspondant à la nouvelle page. Le navigateur client soumet alors cette nouvelle requête en utilisant la méthode GET du protocole HTTP.

La redirection est moins rapide que la navigation classique puisque vous proposer une requête supplémentaire au navigateur. Cependant, la redirection assure au navigateur de mettre à jour systématiquement la page web à visualiser.

  1. La redirection est directive. Elle supplante les règles de navigations classiques. Pour la mettre en oeuvre, il suffit de rajouter la chaîne suivante :
    ?faces-redirect=true
  2. Voici ce que nous pouvons proposer sur un composant <h:commandButton> :
    <h:commandButton value="Accueil" action="accueil?faces-redirect=true" />
  3. Par nature, les composants <h:commandButton> propose des requêtes HTTP de type POST. Si vous désirez plutôt proposer des requêtes de type GET, prenez alors le composant <h:button> comme suit :
    <h:button value="Accueil" outcome="accueil" />

    Vous remarquez la présence de l'attribut outcome de ce composant <h:button> en lieu et place de l'attibut action du composant <h:commandButton>. Il existe une différence fondamentale de comportement. Avec outcome, vous pouvez vous rattacher à une propriété d'un bean géré, mais surtout pas à l'appel d'une méthode. Pour action, vu d'ailleurs son nom, c'est l'inverse, c'est-à-dire que normalement cet attribut est utilisé pour faire appel à la méthode qui gère la navigation.

  4. Dans les requêtes GET, nous avons quelquefois besoin de rajouter des paramètres qui vont servir à exploiter correctement la demande. Le plus facile, à mon avis, est de rajouter une balise <f:param> pour chaque paramètre que vous souhaitez prendre en compte dans votre soumissions :
    <h:button value="Accueil" includeViewParams="true" outcome="accueil">
       <f:param name="utilisateur" value="#{bean.personne}">
       <f:param name="administrateur" value="non">
    </h:button>

Application web qui propose un jeu de tirage aléatoire d'un nombre

Pour terminer ce chapitre, je vous propose de prendre en compte ces notions de navigation avec pour modèle un bean CDI au travers d'un projet spécifique. Je vous propose de faire un petit jeu qui nous permet de retrouver un nombre déterminé aléatoirement avec un nombre de coup et une valeur maximale limites.

Nous allons maintenant suivre toute la procédure pour réaliser ce projet au travers de l'environnement Netbeans.
Mise en place du projet
  1. Création d'un projet de type application Web :

  2. Nom du projet Alea :

  3. Choix du serveur d'applications et des réglages de base :

  4. Prise en compte de la technologie JSF pour cette application web :

Descripteur de déploiement web.xml

L'application web comporte cette fois-ci deux vues, la première (configurer.xhtml) permet de configurer le jeu en spécifiant la valeur limite du nombre à rechercher avec également un nombre de coup limite, la deuxième (alea.xhtml) concerne le jeu lui-même. J'aimerai que la première page soit affichée dès que nous sollicitons cette application web. Voici ce que nous pouvons proposer au niveau du descripteur de dpéloiement :

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>faces/configurer.xhtml</welcome-file>
    </welcome-file-list>
</web-app>
Le bean géré Nombre

Une fois que le projet est en place, la première démarche avant de s'intéresser aux vues, est de s'occuper du modèle qui va réaliser tous les traitements nécessaires en coulisse. Pour changer, par rapport au premier projet de conversion, le modèle sera mis en oeuvre au travers d'un bean CDI.

bean.Nombre.java
package bean;

import javax.inject.*;
import javax.enterprise.context.*;
import java.io.Serializable;

@Named
@SessionScoped
public class Nombre implements Serializable {
   private int valeurMax = 20;
   private int tentativeMax = 4;
   private int valeur;
   private int alea;
   private int tentative;
   private boolean test;

   public int getTentativeMax() { return tentativeMax; }
   public void setTentativeMax(int tentativeMax) { this.tentativeMax = tentativeMax; }

   public int getValeurMax() { return valeurMax; }
   public void setValeurMax(int valeurMax) { this.valeurMax = valeurMax; }

   public int getValeur() { return valeur;  }
   public void setValeur(int valeur) { this.valeur = valeur; }
   public int getTentative() { return tentative; }

   public String recommencer() {
     alea = (int)(Math.random()*valeurMax)+1;
     tentative = 0;
     valeur = 0;
     test = false;
     return "alea.xhtml";
   }
   
   public void calcul() {
     if (tentative<tentativeMax) {         
       tentative++;
       test = alea == valeur;
     }      
   }

   public String getRésultat() {
     if (test)  return "Bravo, vous avez trouvé !"; 
     if (tentative==tentativeMax) return "Désolé, vous avez perdu. La valeur à rechercher est ";
     if (tentative==0) return "Tentez votre chance";     
     else return "Non, ce n'est pas le bon chiffre, refaites un essai.";
   }

   public String getProgression() {
     if (tentative==0 || test) return "";
     return  "Le nombre est plus "+(alea>valeur ? "grand" : "petit");
   }

   public int getAlea() { return alea; }
   public boolean isFin() { return test || tentative == tentativeMax; }
}
  1. Pour cette partie relative au modèle, nous pouvons envisager plusieurs approches quant au nombre de beans gérés à prendre en compte. La solution retenue ici est de n'utiliser qu'un seul bean. Du coup, le code est beaucoup plus volumineux que lors du projet précédent, puisque ce même bean doit être en relation avec deux vues différentes. Par ailleurs, le traitement à réaliser est plus conséquent.
  2. Il s'agit d'un bean CDI. Remarquez cette fois-ci la présence de l'annotaion @Named. Pour sa mise en oeuvre avec Netbeans, nous retrouvons les mêmes boîtes de dialogue, si ce n'est la petite différence quant au choix de la portée de l'objet.
  3. Lors de l'élaboration d'un bean géré, nous devons retrouver, bien entendu, des attributs, pour sauvegarder des informations qui vont être utiles lors du passage d'une requête à l'autre, des propriétés (getters et setters) indispensables pour la communication directe avec les vues, et des méthodes qui vont être appelées pour réaliser des traitements spécifiques ou pour naviguer entre les vues lorsque l'utilisateur clique sur les boutons de soumissions.
  4. La portée choisie ici est une portée de type session. Ce choix est important puisque lorsque nous réglons les valeurs limites dans la page de configuration, il est impératif de conserver ces valeurs lorsque nous basculons vers la page correspondant au jeu. Pour les mêmes raisons, par exemple, il faut bien garder en mémoire le nombre aléatoire tiré pendant toute la phase du jeu.
  5. Les attributs présents dans ce bean, sont les valeurs limites valeurMax et tentativeMax, aléa le nombre aléatoire à rechercher, la valeur proposée par l'utilisateur en cours de jeu, le nombre de tentative qui s'incrémente automatiquement en cours de jeu et enfin test qui détermine si le nombre proposé par l'utilisateur est le bon.
  6. Nous retrouvons la plupart des proriétés en relation avec ces attributs. Toutefois, il n'existe pas de propriété relative à l'attribut test. Ainsi, des attributs peuvent exister sans qu'ils soient systématiquement accessibles directement dans la vue. Cet attribut test reste utile lors des traitements annexes.
  7. A l'inverse, et c'est flagrant ici, nous pouvons proposer des propriétés qui ne sont associées à aucun attribut particulier parce que nous ne désirons pas conserver d'informations particulières. Elles ne sont utiles que pour l'instant présent. C'est le cas des propriétés en lecture seule, résultat, progression et fin, accessibles donc au travers des méthodes respectives getRésultat(), getProgression() et isFin(). Les deux premières propriétés servent pour l'affichage d'informations alors que la dernière permet de savoir si nous somme à la fin du jeu.
  8. Pour terminer avec ce bean, la méthode intéressante, comme nous l'avons déjà évoqué dans ce chapitre, est la méthode recommencer() qui sera sollicitée à chaque fois que nous devons débuter le jeu, soit après la configuration (appel depuis configurer.xhtml), soit lorsque nous désirons tout simplement recommencer une partie (appel depuis alea.xhtml).
  9. Cette méthode permet d'initialiser correctement tous les attributs afin que le jeu débute dans de bonnes conditions, et surtout, quand tout est prêt, c'est la vue alea.xhtml qui est automatiquement appelée au retour de la méthode. Cette requête est prise en compte par le contrôleur qui s'occupe effectivement de cette demande d'affichage. Il s'agit d'une navigation dynamique.
Feuille de style CSS

Pour ce projet également, et pour tous les projets d'ailleurs, il est judicieux de proposer une feuille de style principale afin de bien séparer l'aspect visuel ainsi que la charte graphique avec les fonctionnalités propres des composants de la vue. Ainsi, il sera très facile par la suite de changer rapidement cet aspect pour en proposer un autre plus adapté.

Rappelez-vous que cette feuille de style ne doit pas être placée n'importe où. Vous devez impérativement prévoir au moins un répertoire bien spécifique pour toutes les ressources annexes à votre projet qui se nomme très justement resources :
resources/<identifiant_ressource>
ou
META-INF/resources/<identifiant_ressource>

<identifiant_ressource> est formé de plusieurs sous-répertoire indiqués sous la forme :

[locale/ ] [nomBibliothèque/ ] nomRessource [ /versionRessource]

Je rappelle que tous les éléments entre crochets sont facultatifs. La dernière fois, j'avais proposé de prendre en compte la bibliothèque (css) que je ne renouvelle pas ici :

resources/principal.css
principal.css
root { 
    display: block;
}

body {
    background-color: orange; 
    font-weight: bold; 
    color: maroon; 
}

.saisie {
    text-align:right; 
    background-color:yellow; 
    font-weight:bold; 
    color:green; 
    width: 30px; 
    display: block;
}

.normal {
    font-weight:bold; 
}

h2, h3 {
    background-color: yellow; 
    padding: 5px; 
    border: groove; 
}

.progression {
    color: blue; 
}

.resultat {
    border: groove; 
    padding-right: 3px; 
}
Les vues configurer.xhtml et alea.xhtml

Dans le projet, nous disposons de deux vues :

  1. La première configurer.xhtml qui permet, comme son nom l'indique, de faire les réglages nécessaires sur les valeurs maximales savoir, la valeur maximale du nombre à recherche et le nombre de tentative maximum.

  2. La deuxième alea.xhtml qui s'occupe de jeu proprement dit :

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

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">

    <h:head>
       <title>Recherche d'un nombre aléatoire</title>
    </h:head>

    <h:outputStylesheet name="principal.css" />
    
    <h:body>
       <h:form>
          <h:panelGrid columns="2">
             <h:outputText value="Recherche d'un nombre aléatoire compris entre 1 et  " styleClass="normal" />
             <h:inputText value="#{nombre.valeurMax}" styleClass="saisie"/>
             <h:outputText value="Nombre de tentative possibles " styleClass="normal"/>
             <h:inputText value="#{nombre.tentativeMax}" styleClass="saisie"/>
          </h:panelGrid>
          <hr />
          <h:commandButton value="Commencer le jeu" action="#{nombre.recommencer}" />
       </h:form>
    </h:body>
</html>
  1. Cette page est relativement modeste. Nous retrouvons la même ossature que lors du premier projet. Le fait d'avoir construit une feuille de style séparée permet d'alléger considérablement la vue elle-même.
  2. Dans ce projet, encore plus que pour le premier, une feuille de style est vraiment intéressante, puisqu'elle est utilisée pour deux vues différentes. Cela permet de factoriser les réglages nécessaires dans un même endroit et d'éviter ainsi les duplications de code.
  3. La prise en compte de la feuille de style se fait toujours au moyen de la balise <h:outputStylesheet name="principal.css" />. Cette fois-ci toutefois, l'attribut library n'est pas utilisé puisque nous avons placé la feuille de style directement dans le répertoire resources sans proposer de libraire interne.
  4. La grande nouveauté concerne la balise <h:panelGrid columns="2">. Ce composant est l'équivalent d'un panneau intermédiaire qui permet d'aligner tous les autres composants qui se trouve à l'intérieur, et dans ce cas précis suivant deux colonnes.
  5. Avec le composant <h:commandButton value="Commencer le jeu" action="#{nombre.recommencer}" />, lorsque l'utilisateur clique sur le bouton, c'est la méthode recommencer() du bean CDI nombre qui est appelée, et cette dernière comme nous l'avons vu, après avoir remis à zéro les attributs concernant le jeu, demande au contrôleur d'afficher la page alea.xhtml.
    public String recommencer() {
       alea = (int)(Math.random()*valeurMax)+1;
       tentative = 0;
       valeur = 0;
       test = false;
       return "alea.xhtml";
    }
alea.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">

    <h:head>
       <title>Recherche d'un nombre aléatoire</title>
    </h:head>
    
    <h:outputStylesheet name="principal.css" />

    <h:body>
       <h:form>
          <h:panelGrid columns="3">
             <h:outputText value="Votre nombre : " />
             <h:inputText value="#{nombre.valeur}" styleClass="saisie" />
             <h:outputText value="#{nombre.progression}" styleClass="progression" />
             <h:outputFormat value="Tentative{0, choice, 0#|2#s} : ">
                <f:param value="#{nombre.tentative}" />
             </h:outputFormat>
             <h:inputText value="#{nombre.tentative}" styleClass="saisie" disabled="true" />
          </h:panelGrid>
          <hr />
          <h:commandButton value="Valider votre nombre" action="#{nombre.calcul}" disabled="#{nombre.fin}" />
          &nbsp;
          <h:commandButton value="Recommencer" action="#{nombre.recommencer}" />
          &nbsp;
          <h:commandButton value="Changer les valeurs limites" action="configurer.xhtml" />
          <hr />
          <h2>
            <h:outputText value="#{nombre.résultat}" />
            <h:outputText value="#{nombre.alea}" styleClass="saisie" rendered="#{nombre.fin}" />
          </h2>
       </h:form>
    </h:body>
</html>
  1. Ici aussi, je vous propose quelques remarques. Sur cette dernière version de JSF, il n'est pas obligé de prendre systématiquement une balise <h:outputText value="Votre nombre : " />. Nous aurions pu écrire directement Votre nombre : . Si vous avez à prendre en compte des styles CSS, il est alors intéressant de les utiliser, comme ici <h:outputText value="#{nombre.progression}" styleClass="progression" />.
  2. Le composant <h:outputFormat value="Tentative{0, choice, 0#|2#s} : "> permet de proposer d'afficher des textes paramétrés, comme ici de rajouter un s à partir de la deuxième tentative. A l'intérieur de ce composant, vous devez spécifier l'ensemble des paramètres qui sert de critère de calcul au travers du composant <f:param value="#{nombre.tentative}" />.
  3. Le bouton <h:commandButton value="Valider votre nombre" action="#{nombre.calcul}" disabled="#{nombre.fin}" /> peut être grisé, et donc non actif, au moyen de l'attribut disabled, ici par exemple lorsque nous somme à la fin du jeu.
  4. Lorsque vous désirez faire une navigation statique, c'est-à-dire lorsque vous désirez atteindre une autre page sans faire de traitement préliminaire ou sans condition particulière, il suffit simplement de préciser la page dans l'attribut action du composants <h:commandButton value="Changer les valeurs limites" action="configurer.xhtml" />.
  5. Enfin, il est possible d'afficher un composant sous certaines conditions, ceci également très simplement, au moyen de l'attribut rendered (présent sur tous les composants visuels). Par exemple, lorsque nous sommes à la fin du jeu, nous pouvons montrer le nombre à rechercher <h:outputText value="#{nombre.alea}" styleClass="saisie" rendered="#{nombre.fin}" />.
Conclusion

 

Choix du chapitre Bibliothèques de balises JSF

L'architecture JSF est conçue pour être indépendante de tout protocole ou langage à marqueurs particulier et pour écrire des applications pour les clients HTML qui communiquent via HTTP. Une interface utilisateur pour une page web donnée est créée en assemblant des composants qui fournissent des fonctionnalités spécifiques afin d'interagir avec l'utilisateur (labels, cases à cocher, etc.) - JSF met à disposition un certain nombre de classes composants couvrant la plupart des besoins classiques.

Une page est une arborescence de classes héritant de javax.faces.component.UIComponent ayant des propriétés, des méthodes et des événements. La racine de l'arbre est une instance de UIViewRoot et tous les autres composants respectent une relation d'héritage.

Lors de la version précédente, JSF proposait uniquement deux librairies de balises : core et HTML, la version JSF 2.0 couvre maintenant six librairies différentes qui possèdent plus d'une centaine de marqueurs spécifiques. Le tableau ci-dessous nous rappelle ces différentes librairies. Dans ce chapitre toutefois, nous nous intéresserons uniquement sur les bibliothèques core et HTML.

URI Préfixe classique Description
http://java.sun.com/jsf/html h Contient les composants et leurs rendus HTML (h:commandButton, h:inputText, etc.)
http://java.sun.com/jsf/core f Contient les actions personnalisées indépendantes d'un rendu particulier (f:convertNumber, f:validateDoubleRange, f:param, etc.)
http://java.sun.com/jsf/facelets ui Marqueurs pour le support des modèles de page.
http://java.sun.com/jsf/composite composite Sert à déclarer et à définir des nouveaux composants personnalisés.
http://java.sun.com/jsp/jstl/core c Les pages Facelets peuvent éventuellement utiliser certains marqueurs issus de JSP (c:if, c:forEach et c:catch).
http://java.sun.com/jsp/jstl/functions fn Les pages Facelets peuvent utiliser tous les marqueurs de fonctions issus de la technologie JSP.

Bibliothèque core

La bibliothèque core (coeur) contient des balises qui sont indépendante du rendu HTML. Elles sont utiles pour proposer des traitements et des réglages spécifiques pour les balises relatives à la vue (bibliothèque HTML).

Balises Description
<f:attribute> Propose un attribut particulier (clé/valeur) sur une balise parente.
<f:param> Spécifie un paramètre particulier associé à la chaîne paramétrée proposée par la balise parente.
<f:facet> Gestion d'une zone délimitée de la page Web.
<f:actionListener> Ajoute un événement de type validation sur un composant parent.
<f:setPropertyActionListener> Propose une propriété sur un événement de type validation associé à un composant parent.
<f:valueChangeListener> Ajoute un événement de type changement de valeur sur un composant parent.
<f:phaseListener> Ajoute un événement de type changement de phase sur un composant parent.
<f:event> Ajoute un système d'événement sur un composant parent.
<f:converter> Ajoute un convertisseur personnalisé sur un composant parent.
<f:convertDateTime> Ajoute un convertisseur prédéfini de type date ou heure sur un composant parent.
<f:convertNumber> Ajoute un convertisseur prédéfini numérique sur un composant parent.
<f:validator> Ajoute un validateur personnalisé à un composant parent.
<f:validateDoubleRange> Ajoute un valideur prédéfini qui limite les valeurs numériques réelles sur un composant parent.
<f:validateLength> Ajoute un valideur prédéfini qui limite le nombre de caractères saisie sur un composant parent.
<f:validateLongRange> Ajoute un valideur prédéfini qui limite les valeurs numériques entières sur un composant parent.
<f:validateRequired> Vérifie qu'une valeur est présente sur un composant parent.
<f:validateRegex> Vérifie que la valeur saisie respecte une expression régulière sur un composant parent.
<f:validateBean> Utilise un bean de validation qui impose un certain nombre d'éléments à prendre en compte.
<f:loadBundle> Prise en compte d'une ressource qui propose l'ensemble des messages paramétrés (gestion de la nationalité, par exemple).
<f:selectItems> Propose l'ensemble d'éléments qui vont remplir les radios boutons, les cases à cocher, la liste déroulante, etc.
<f:selectItem> Propose un élément spécifique.
<f:verbatim> Permet de donner le texte qui possède des balises sans interprétation particulière.
<f:viewParam> Défini un paramètre pour la vue qui sera initialisé à l'aide du paramètre de la requête.
<f:ajax> Permet d'implémenter des commandes ajax.
<f:view> Utile si nous désirons prendre en compte la nationalité ou la gestion événementielle de phase.
Nous allons aborder certains de ces marqueurs afin d'expliquer leurs fonctionnement internes :
Attributs, paramètres et facettes

Les balises <f:attribute>, <f:param> et <f:facet> sont généralement proposées pour rajouter des informations sur un composant parent. Chaque composant peut enregistrer un nombre arbitraire de paires nom / valeur.

  1. <f:attribute> : Ainsi, à l'aide de cette balise, vous pouvez proposer un attribut particulier dans la page web (la vue) qui sera ensuite exploitée par le modèle pour réaliser un traitement spécifique :
    <h:outputText value="#{agent.téléphone}">
         <f:converter converterId="numéroTéléphone" />
         <f:param name="séparateur" value="-" />
    </h:outputText>
    

    Les attributs sont généralement utiles pour les convertisseurs personnalisés. Une fois que l'attribut spécifié est associé à la balise parente, le convertisseur est capable de le retrouver aisément, comme ceci :

    séparateur = (String) component.getAttributes().get("séparateur");
       
    // component est issu de la méthode String getAsString(FacesContext context, UIComponent component, Object value)
  2. <f:param> : Cette balise permet également de définir une paire nom / valeur. Le nom correspond cette fois-ci plutôt à un numéro, suivant l'ordre où nous plaçons chacunes des balises <f:param>. Typiquement, cette balise peut être associée à la balise parente <h:outputFormat> comme nous l'avons déjà utilisée dans le projet précédent :
    <h:outputFormat value="Tentative{0, choice, 0#|2#s} : ">
         <f:param value="#{nombre.tentative}" />
    </h:outputFormat>     

    Dans ce cas, il n'existe qu'un seul paramètre, correspondant donc à la position 0, et dont la valeur récupérée dans la propriété tentative de nombre est soumise à la balise parente afin que son évaluation détermine la mise en place du pluriel ou pas.

  3. <f:facet> : Cette balise est utilisée dans des cas très particulier pour spécifier une apparence adaptée. Ainsi, par exemple, dans une page web, nous retrouvons presque systématiquement deux facettes nommées head et body. Typiquement, nous utilisons cette balise presque uniquement pour proposer des facettes différentes à un tableau (<h:dataTable>). Ainsi, nous pouvons proposer un tableau uniforme, ou avec une en-tête, ou avec un pied, ou avec un titre, ou bien avec une combinaison de toutes ces possibilités. Pour obtenir ces différents aspects, vous placez respectivement les valeurs header, footer ou caption dans cette balise <f:facet> suivant le cas.
    <h:dataTable value="#{auteurs}" var="élément"  headerClass="inverse" columnClasses="ligne">
        <h:column>
             <f:facet name="header">Nom</f:facet>
             #{élément.nom}
        </h:column>
         <h:column>
             <f:facet name="header">Prénom</f:facet>
             #{élément.prénom}
         </h:column>
    </dataTable>
    

    Dans ce tableau, par exemple, nous plaçons une en-tête d'une couleur différente que les autres lignes du tableau, dont la première colonne sera intitulé Nom et la deuxième Prénom. Par ailleurs, et de façon automatique, la facette de type header permet de centrer dans la colonne la valeur que nous lui proposons.

Bibliothèque html

Cette bibliothèque intègre cette fois-ci des composants visuels, qui assurent donc le rendu de la page web. Nous pouvons les regrouper par catégories :

  1. Structure de la page HTML (head, body, form, outputStylesheet, outputScript)
  2. Dispositions (panelGrid, panelGroup)
  3. Tables (dataTable et column)
  4. Les entrées : (input...)
  5. Les sorties (output...)
  6. Les images (graphicImage)
  7. Les commandes (commandButton et commandLink)
  8. Les requêtes GET HTTP (button, link, outputLink)
  9. Les sélections (checkbox, listbox, menu, radio)
  10. Messages d'erreur (message, messages)
Nous allons prendre le temps de bien analyser chacun de ces groupes de balises. Au préalable, je vous invite à consulter l'ensemble des attributs qui leurs sont communs. Il existe trois types d'attribut :
  1. Les attributs de base,
  2. Les attributs qui ont leurs correspondances HTML,
  3. Les attributs associés aux événements DHTML.
Attributs de base

Dans le tableau ci-dessous, nous découvrons l'ensemble des attributs qui sont partagés par la majorité des marqueurs JSF.

Attributs Description
id Permet d'identifier le composant pour interagir avec un autre.
binding Lie ce marqueur (ce composant dans sa globalité) directement avec le bean géré, et permet donc une interaction en coulisse.
rendered Attribut très intéressant qui permet d'afficher ou pas la balise (équivalent d'un if). Valeurs possibles true ou false.
value Attribut certainement le plus utilisé, qui permet d'être en relation directe avec une propriété particulière du bean géré déterminé par une expression EL (#{bean.propriété}).
valueChangeListener Un écouteur spécifique qui entre en action lors d'un changement de valeur. La méthode désignée dans cet attribut est alors automatiquement sollicitée.
converter Mise en relation avec un convertisseur personnalisé (classe qui implémente l'interface Converter) et qui permet de passer d'une chaîne de caractères (seule type compatible avec le protocole HTTP) vers un autre type quelconque et vice versa.
validator Mise en relation avec un validateur personnalisé (classe qui implémente l'interface Validator, ou éventuellement une méthode avec une signature spécifique) qui permet d'envoyer la valeur désignée dans l'attribut value au bean géré (qui travaille en coulisse) que si la valeur est correcte.
required Une valeur est impérativement requise dans ce champ pour passer la phase de validation.
converterMessage, validatorMessage, requiredMessage Messages personnalisés qui s'affichent automatiquement (en relation avec les balises <h:message> ou <h:messages>) lorsque un problème survient lors de la phase de conversion, de validation ou lorsque q'une valeur de saisie est oubliée.
  1. Tous les composants, quels qu'ils soient, peuvent utiliser les attributs id, binding et rendered que nous allons tout de suite analyser.
  2. Les attributs value et converter permettent, de concert, de spécifier comment la propriété du bean proposée puisse être automatiquement convertie sous forme de chaîne de caractères, afin d'être transmis correctement par le protocole HTTP pour la vue, et comment, en coulisse avec le modèle, manipuler cette propriété par le bean géré pour travailler directement avec le type adéquat.
  3. Enfin, les attributs validator, required et valueChangeListener sont disponibles pour les composants de saisie (Entrées) qui réalisent un contrôle des valeurs saisies afin de faire réagir éventuellement l'opérateur pour qu'il corrige le tir afin que ces dernières soient finalement considérées comme valides pour le traitement souhaité.
Identification et liaisons entre composants

L'attribut polyvalent id permet de réaliser les choses suivantes :

  1. L'accès au composant identifié par un autre composant de la page web. L'exemple très fréquent consiste à identifier un composant d'entrée afin d'afficher un message d'erreur adapté au cas où la saisie ne serait pas correcte :
    <h:inputText value="#{conv.euro}" 
                                      id="euro"
                                      converterMessage="Il faut une valeur monétaire"
                                      validatorMessage="Uniquement les nombres positifs"
                                      required="true"
                                      requiredMessage="Précisez votre valeur monétaire">
                    
                    <f:convertNumber type="currency" currencySymbol="€"/>
                    <f:validateDoubleRange minimum="0.0" />
    </h:inputText>
                
    <h:message for="euro" styleClass="erreur"/>
    
  2. Obtenir en coulisse les références de ce composant dans le code Java du bean géré. Par exemple, si nous désirons accéder au composant désigné par euro au travers d'un écouteur, voici ce que nous pouvons écrire :
    UIComponent composant =  event.getComponent().findComponent("euro");
    

    Attention, pour que ce code soit exploitable, le composant qui génère l'événement doit se trouver dans le même formulaire (<h:form>) que le composant identifié, sinon le composant ne sera pas accessible.

  3. Accès aux éléments HTML au travers de scripts.

Il existe une autre démarche pour accéder à un composant de la vue depuis le code Java. Il suffit de définir une propriété dans le modèle et ensuite de spécifier l'attribut binding du composant en connexion avec cette propriété particulière.

<h:inputText binding="#{beanGéré.propriété}" ... />

L'attribut binding est spécifié à l'aide d'une expression EL. Cette expression, comme d'habitude fait référence à une propriété particulière du bean géré, qui d'ailleurs doit être à la fois accessible en lecture et en écriture.

private UIComponent propriété =  new UIInput(); // création d'un objet qui correspond au type du composant visuel à représenter
public UIComponent getPropriété() { return propriété; }
public void setPropriété(UIComponent propriété) { this.propriété = propriété; }

Nous pourrions nous poser la question sur l'intérêt d'un tel attribut binding puisque value est généralement suffisant. Dans des cas très particulier, il peut être intéressant de faire référence au composant de la vue dans son ensemble plutôt que de rester uniquement sur la propriété, interne donc au bean. Nous pourrions du coup envisager de faire du traitement spécifique sur ce composant visuel pour faire en sorte qu'il soit par exemple non éditable, non visible, grisé, etc. directement depuis le bean géré.

Effectivement, tous les composant visuels sont avant tout des classes avec donc leurs propres méthodes. il suffit de faire appel à ces méthodes pour changer le comportement de ces éléments visuel, en coulisse, directement depuis le modèle.

Valeurs, convertisseurs et validateurs

Les entrées, les sorties, les commandes et tables de données manipulent toutes des valeurs. L'attribut value permet d'être en relation avec ces valeurs. Il peut s'agir d'une valeur littérale comme d'une valeur issue d'une propriété :

<h:commandButton value="Intitulé du bouton" ... />
<h:inputText value="#{beanGéré.propriété}" ... />
  1. L'attribut converter, proposé pour les balises d'entrée et de sortie, permet de rattacher un convertisseur personnalisé à un composant.
  2. Les balises d'entrée peuvent également proposée l'attribut validator afin de rattacher un validateur personnalisé à un composant.
  3. Ces convertisseurs et validateurs sont bien utiles afin d'éviter que le bean géré ne rentre dans sa phase de traitement d'informations tant que les valeurs proposées ne sont pas valides.
Rendu conditionnel

Vous pouvez utiliser l'attribut rendered pour inclure ou exclure des composants dans votre page web, d'avoir ainsi un rendu conditionnel. Par exemple, nous pourrions afficher un bouton de déconnexion que si nous sommes sûr que l'utilisateur s'est déjà logger :

<h:commandButton value="Deconnexion"  rendered="#{utilisateur.connecté}" action="#{utilisateur.seDéconnecter}" />
<h:inputText... />

La condition du rendu peut s'appliquer sur un groupe de composants, au travers des balises <h:panelGrid> ou <h:panelGroup> qui possèdent cet attribut rendered.

<h:pannelGrid columns="2"  rendered="#{bean.ongletSélectionné == 'vidéo'}">
   <h:outputText... />
   <h:commandButton... />
   ...
</h:pannelGrid>
Attributs HTML

Ces types d'attributs sont particuliers puisqu'ils ne sont pas interprétés par les composants JSF mais sont directement traduits dans les balises HTML correspondantes. Il s'agit exactement des mêmes attributs qui sont proposés par les balises HTML standard.

<h:inputText size="25"  value="#{bean.propriété}"  /> // code proposé en JSF
...
<input size="25"  type="text"  ... /> // balise générée automatiquement par le système pour l'envoyer ensuite au client

Ainsi, par exemple, si nous proposons l'attribut size à une balise JSF, nous la retrouvons ensuite intégralement, sans interprétation particulière, dans la balise HTML équivalente. Rappelez vous que la vue JSF reste sur le serveur et que le document envoyé est une page web avec des balises HTML standard (le client n'a besoin d'installer de pluggin particulier, un simple navigateur suffit).

Attributs Description
alt Prévoit un texte alternatif dans le cas où une image par exemple n'est pas présente.
border Largeur de bordure en pixel.
dir Direction du texte. Les valeurs valides sont "LTR" (Left To Right) et "RTL" (Right To Left).
disabled Permet de grisé un élément pour le rendre inactif à l'image des zones de saisie et des boutons de commande.
maxlength Spécifie le nombre maximum de caractères possible sur une zone de saisie.
readonly Empêche de réaliser une édition sur une zone de saisie.
rows Spécifie le nombre de lignes pour une zone de texte.
size Taille pour une zone de saisie.
style Possibilité d'introduire un style CSS uniquement sur cette balise.
styleClass Propose un style CSS prédéfini dans une feuille de style séparée.
title Description du marqueur qui typiquement est traduit sous forme de bule d'aide par les navigateurs.
width Largeur en pixels de ce composant.
Styles CSS

Nous pouvons donc introduire des styles CSS, soit directement (attribut style), soit à partir d'une déclaration déjà faite antérieurement (attribut styleClass) qui influence, bien entendu, le rendu du composant.

<h:inputText styleClass="bordure"  value="#{bean.propriété}"  />
<h:inputText style="border: thin solid blue"  value="#{bean.propriété}"  />
Gestion des ressources

La plupart des composants ont besoin de ressources externes pour s'afficher correctement : <h:graphicImage> a besoin d'une image, <h:commandButton> peut également afficher une image pour représenter le bouton, <h:outputScript> référence un fichier JavaScript et surtout les composants peuvent également appliquer des styles CSS.

Avec JSF, une ressource est un élément statique qui peut être transmis aux éléments afin d'être affiché (images) ou traité (CSS) par le navigateur. JSF 2.0 permet d'assembler directement les ressources dans un fichier jar séparé, avec un numéro de version et une locale, et de les placer à la racine de l'application web, sous le répertoire suivant :
resources/<identifiant_ressource>
ou
META-INF/resources/<identifiant_ressource>

<identifiant_ressource> est formé de plusieurs sous-répertoire indiqués sous la forme :

[locale/ ] [nomBibliothèque/ ] nomRessource [ /versionRessource]

Tous les éléments entre crochets sont facultatifs. La locale est le code du langage, suivi éventuellement d'un code de pays (en, en_US, pt, pt_BR). Comme l'indique cette syntaxe, vous pouvez ajouter un numéro de version à la bibliothèque ou à la ressource elle-même.

Voici quelques exemples :

resources/css/principal.css
resources/images/logo.png
resources/javascript/jsf.js  

Voici maintenant comment prendre en compte ces différentes ressources :

<h:outputStylesheet library="css"  name="principal.css"  />
<h:outputScript library="javascript"  name="jsf.js"  target="head"  />
<h:graphicImage library="images"  name="logo.png"  />
Evénements DHTML

Il est possible de rajouter un comportement plus dynamique sur vos pages web, comme des animations ou la prise en compte en temps réel d'un certain nombre d'événements. C'est ce que nous appelons communément le DHTML. En réalité, en coulisse, dès que nous évoquons des pages DHTML, nous faisons référence à du javascript.

Tous ces événement javascript sont disponibles sur chacun des composants JSF au travers d'attributs spécifiques dont voici la liste. Vous devez préciser ensuite l'action que vous désirez réaliser à l'issu de ces événements.

Attributs Description
onblur L'élément perd le focus.
onchange La variable de la zone de saisie change de valeur.
onclick Prise en compte du clic de la souris.
ondblclick Prise en compte du double clic de la souris.
onfocus L'élément reprend le focus.
onkeydown L'opérateur est en train d'appuyer sur une touche.
onkeypress L'opérateur tape sur une touche.
onkeyup L'opérateur relache une touche.
onload Au moment où la page est chargée.
onmousedown En train d'appuyer sur un bouton de la souris.
onmousemove La souris se déplace sur l'élément.
onmouseout La souris sort de la zone de l'élément.
onmouseover La souris passe au dessus de l'élément.
onmouseup On relache un bouton de la souris
onreset Le formulaire est remise à zéro.
onselect Texte sélectionné dans une zone de saisie.
onsubmit Le formulaire est envoyé pour traitement.
onunload Au moment où nous quittons la page en cours.
Structure de la page HTML (<h:head>, <h:body>, <h:form>, <h:outputScript>, <h:outputStylesheet>)

Tous ces marqueurs n'ont pas de représentation graphiques mais possèdent un équivalent HTML (voir tableau ci-dessous). Bien que les marqueurs natifs HTML puissent être utilisés directement sans problème, les marqueurs JSF possèdent des attributs supplémentaires qui facilitent le développement.

Vous pouvez, par exemple, ajouter une bibliothèque JavaScript à l'aide du marqueur HTML Standard <script type="text/JavaScript">, mais le marqueur <h:outputScript library="javascript" name="jsf.js" target="head" /> de JSF permet d'utiliser la nouvelle gestion des ressources, comme nous venons de le découvrir plus haut.

Balises Description
<h:body> Génère un élément <body> HTML.
<h:head> Génère un élément <head> HTML.
<h:form> Génère un élément <form> HTML.
<h:outputScript> Génère un élément <script> HTML.
<h:outputStylesheet> Génère un élément <link> HTML.

Typiquement, les applications web fonctionnent en soumettant des requêtes issues d'un formulaire. JSF n'échappe pas à cette règle de base et doit proposer au moins une balise <h:form>.

  1. Bien que la balise <form> du HTML standard propose les attributs method et action qu'il est nécessaire de spécifier, le marqueur équivalent JSF <h:form> n'a pas besoin de ce genre d'informations.
  2. Il faut dire que dans le cas de JSF, l'attribut action n'a pas de sens puisque le traitement souhaité par les requêtes issues de ce formulaire peuvent éventuellement être réalisé par plusieurs beans gérés.
  3. Comme nous pouvons sauvegarder l'état complet d'un ou plusieurs beans gérés chez le client, une option qui s'implémente au travers de champs cachés, les soumissions de formulaire ne peuvent être effectuées par la méthode GET HTTP.
  4. Effectivement, le contenu de ces champs cachés peut dépasser largement les capacités du buffer relatif à l'envoi de la requête, aussi les formulaires JSF sont toujours implémentés à l'aide des méthodes POST HTTP.
Grilles et tableaux

Les données doivent souvent être affichées sous forme de tableau. JSF fournit donc le marqueur <h:dataTable> permettant ainsi de parcourir une liste d'éléments (dont la taille est variable) afin de générer automatiquement un tableau. Les tableaux peuvent également servir de gestion de disposition des composants afin de créer une interface utilisateur sous forme de grille. Dans ce cas, vous pouvez utiliser les marqueurs <h:panelGrid> et <h:panelGroup> pour disposer les composants à votre guise.

Balises Description
<h:dataTable> Représente un ensemble de données qui seront affichés dans un élément <table> HTML.
<h:column> Produit une colonne de données dans un composant <h:dataTable>.
<h:panelGrid> Produit également un élément <table> HTML.
<h:panelGroup> Conteneur de composants pouvant s'imbriquer dans un <h:panelGrid>.

Les tableaux dynamiques composés de la balise <h:dataTable> et des balises <h:column> feront parti d'un chapitre à part entière au cours de cette étude.

A la différence de <h:dataTable>, le marqueur <h:panelGrid> n'utilise pas de modèle de données sous-jacent pour produire des lignes de données - c'est un conteneur permettant d'insérer les autres composants JSF dans une grille de lignes et de colonnes.

Vous pouvez préciser le nombre de colonnes que comportera cette grille de disposition à l'aide de l'attribut column. <h:panelGrid> déterminera automatiquement le nombre de lignes nécessaire suivant le nombre de composants introduits. Il place alors les composants dans l'ordre précisé de la gauche vers la droite et du haut vers le bas.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">

    <h:body style="background-color: orange">
        <h:form>
            <h:outputText value="Identification" />
            <hr />
            <h:panelGrid columns="2" border="1">
                <h:outputText value="Nom :" />
                <h:inputText value="" />
                <h:outputText value="Prénom :" />
                <h:inputText value="" />
                <h:outputText value="Mot de passe :" />
                <h:inputSecret value="" />
            </h:panelGrid>
            <hr />
            <h:commandButton value="Connexion" />
        </h:form>
    </h:body>
</html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">

    <h:body style="background-color: orange">
        <h:form>
            <h:outputText value="Identification" />
            <hr />
            <h:panelGrid columns="2"> 
                <h:outputText value="Nom :" />
                <h:inputText value="" />
                <h:outputText value="Prénom :" />
                <h:inputText value="" />
                <h:outputText value="Mot de passe :" />
                <h:inputSecret value="" />
            </h:panelGrid>
            <hr />
            <h:commandButton value="Connexion" />
        </h:form>
    </h:body>
</html>

Pour combiner plusieurs composant dans la même colonne, utilisez un <h:panelGroup> qui produira ses fils comme un seul et unique composant. Vous pouvez également définir un en-tête et un pied à l'aide du marqueur spécial <f:facet>.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <style type="text/css">
        body {background-color: green; color: yellow; }
        .ligne {background-color: #005500; }
        .saisie {background-color: greenyellow; }
    </style>

    <h:body>
        <h:form>
            <h:panelGrid columns="2" styleClass="ligne">
                <f:facet name="header">
                    <h:outputText value="Identification" />
                </f:facet>
                <h:outputText value="Nom :" />
                <h:inputText value="" styleClass="saisie"/>
                <h:outputText value="Prénom :" />
                <h:inputText value="" styleClass="saisie" />                
                <h:outputText value="Mot de passe :" />
                <h:panelGroup>
                    <h:inputSecret value="" styleClass="saisie" />   
                    <h:outputText value=" (4 mini)" />
                </h:panelGroup>
                <f:facet name="footer">
                    <h:commandButton value="Connexion" />
                </f:facet>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <style type="text/css">
        body {background-color: green; color: yellow; }
        .cadre {background-color: #005500; }
        .ligne {background-color: #007000; }
        .saisie {background-color: greenyellow; }
    </style>

    <h:body>
        <h:form>
            <h:panelGrid columns="2" rowClasses="ligne" 
                                                      footerClass="cadre" 
                                                      headerClass="cadre">
                <f:facet name="header">
                    <h:outputText value="Identification" />
                </f:facet>
                <h:outputText value="Nom :" />
                <h:inputText value="" styleClass="saisie"/>
                <h:outputText value="Prénom :" />
                <h:inputText value="" styleClass="saisie" />                
                <h:outputText value="Mot de passe :" />
                <h:panelGroup>
                    <h:inputSecret value="" styleClass="saisie" size="12"/>   
                    <h:outputText value=" (4 mini)" />
                </h:panelGroup>
                <f:facet name="footer">
                    <h:commandButton value="Connexion" />
                </f:facet>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

Il est possible de rajouter des styles personnalisés sur différentes parties de la table :

  1. La table dans son entier avec l'attribut styleClass.
  2. L'entête avec l'attribut headerClass.
  3. Le pied avec l'attribut footerClass.
  4. Les lignes avec l'attribut rowClasses.
  5. Les colonnes avec l'attribut columnClasses.

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <style type="text/css">
        body {background-color: green; color: yellow; }
        .cadre {background-color: #005500; }
        .ligne {background-color: #007000; }
        .saisie {background-color: greenyellow; }
    </style>

    <h:body>
        <h:form>
            <h:panelGrid columns="2" rowClasses="ligne, cadre" 
                         footerClass="cadre" headerClass="cadre">
                <f:facet name="header">
                    <h:outputText value="Identification" />
                </f:facet>
                <h:outputText value="Nom :" />
                <h:inputText value="" styleClass="saisie"/>
                <h:outputText value="Prénom :" />
                <h:inputText value="" styleClass="saisie" />                
                <h:outputText value="Mot de passe :" />
                <h:panelGroup>
                    <h:inputSecret value="" styleClass="saisie" size="12"/>   
                    <h:outputText value=" (4 mini)" />
                </h:panelGroup>
                <f:facet name="footer">
                    <h:commandButton value="Connexion" />
                </f:facet>
            </h:panelGrid>
        </h:form>
    </h:body>
</html>

Vous remarquez que les attributs rowClasses et columnClasses possèdent un "s" à la fin, contrairement aux autres. Cette particularité est importante puisqu'il est possible de proposer plusieurs styles pour le même attribut. L'intérêt ici est de permettre une alternance de couleurs entre les lignes ou les colonnes à l'image de certains listings.

Les entrées

les entrées sont des composants qui affichent leur valeur courante (actuelle) et permettent à l'utilisateur de saisir différentes informations textuelles. Il peut s'agir de champs de saisie, de zone de texte ou de composants pour entrer un mot de passe ou des données cachées.

Balises Description
<h:inputHidden> Représente un élément d'entrée HTML de type caché (non affiché).
<h:inputSecret> Représente un élément d'entrée HTML de type mot de passe. Pour des raisons de sécurité, tout ce qui est saisi ne s'affiche pas (des points apparaissent à la place de chaque caractère introduit), sauf si la propriété redisplay vaut true.
<h:inputText> Représente un élément d'entrée HTML de type texte.
<h:inputTextarea> Représente une zone de texte HTML.

De nombreuses pages web contiennent des formulaires pour que l'utilisateur puisse saisir des données ou se connecter en fournissant un mot de passe. En outre les composants d'entrée utilisent plusieurs attributs permettant de modifier leur longueur, leur contenu ou leur aspect.

Attributs Description
cols Nombre de colonnes, utile uniquement pour la balise <h:inputTextarea>.
immediate Permet de passer outre les phases de conversion et de validations.
redisplay Uniquement pour la balise <h:inputSecret>. Lorsque cet attribut est validé (true), la valeur du champ est réaffichée au recharchement de la page.
required Une valeur est impérativement requise dans ce champ pour passer la phase de validation.
rows Nombre de lignes, utile uniquement pour la balise <h:inputTextarea>.
label Description du composant lorsqu'un message d'erreur est sollicité. Ne s'applique pas à la balise <h:inputHidden>.
valueChangeListener Un écouteur spécifique qui entre en action lors d'un changement de valeur.
<h:inputHidden value="#{bean.valeurCachéeRécupérée}" />
<h:inputSecret value="#{bean.motDePasse}" maxlength="8" />
<h:inputText value="#{bean.propriété}" />
<h:inputText value="#{bean.propriété}" size="40" />
<h:inputTextarea value="#{bean.propriété}" rows="5"  cols="20" />
  1. Tous les composants possèdent un attribut value pour fixer leur valeur par défaut, et surtout pour enregistrer la valeur saisie dans la propriété choisie. Ce qui sous-entend que la propriété doit impérativement être accessible à la fois en lecture et en écriture. Les attributs immediate, required et valueChangeListener existent pour tous ces marqueurs d'entrée.
  2. L'attribut maxLength permet de s'assurer que le texte ne dépasse pas une longueur donnée et l'attribut size modifie la taille par défaut du composant. (voici ci-dessous l'apparence du code proposé).
  3. Trois attributs sont spécifiques à un seul marqueur. Il s'agit de cols et rows qui permettent de proposer une dimension (un nombre de lignes et de colonnes) à la balise <h:inputTextArea>. Le dernier attribut est redisplay qui est éventuellement utilisé par la balise <h:inputSecret>. Cet attribut prend une valeur booléenne qui permet, en cas de validation, de retenir le mot de passe pour qu'il soit automatiquement reproposer (de façon caché, bien évidemment) à la prochaine demande de connexion.
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">

    <h:body style="background-color: green; color: yellow; font-weight: bold">
        <h:form>
            <h:panelGrid columns="2">
                <h:outputText value="Lecture seule :" />
                <h:inputText value="#{test.valeur}" readonly="true"/>
                <h:outputText value="Réaffichage du mot de passe :" />
                <h:inputSecret value="#{test.motPasse}" redisplay="true" />       
                <h:outputText value="Pas de réaffichage :" />
                <h:inputSecret value="#{test.motPasse}" redisplay="false" />                 
                <h:outputText value="Style personnalisé :" />
                <h:inputText value="#{test.valeur}" 
                                    style="background: yellow; color: green"/>
                <h:outputText value="Taille limite :" />
                <h:inputText value="#{test.valeur}" size="5"/>
                <h:outputText value="Nombre de caractères limités :" />
                <h:inputText value="#{test.valeur}" size="10" maxlength="6" />
            </h:panelGrid>
            <hr />
            <h:commandButton value="Soumettre" />
        </h:form>
    </h:body>
</html>

JSF supporte les champs cachés à l'aide du marqueur <h:intputHidden>. Les champs cachés sont souvent utilisés avec des actions JavaScript pour interagir avec le serveur.

Les sorties

Les composants de sortie affichent une valeur qui peut éventuellement avoir été obtenue à partir d'un bean géré, une valeur issue d'une expression EL quelconque ou un simple texte littéral. L'utilisateur ne peut pas modifier ce contenu car il est bien entendu en lecture seule.

Balises Description
<h:outputLabel> Génère une balise <label> HTML.
<h:outputLink> Génère une balise <a> HTML.
<h:outputText> Génère tout simplement un texte littéral.
<h:outputFormat> Génère un texte littéral paramétré.
<h:graphicsImage> Génère une balise <img> HTML.

La plupart des pages web affichent du texte. Pour ce faire, vous pouvez utiliser des balises HTML classiques mais, grâce à EL, les marqueurs de sortie de JSF permettent d'afficher le contenu d'une propriété liée à un bean géré.

<h:outputText>

La balise <h:outputText> est certainement le marqueur le plus simple de JSF. Avec une simple poignée d'attributs, il ne génère aucune balise HTML particulière. Il produit un simple texte. Il existe toutefois une seule exception à cette règle. En effet, lorsque nous demandons à prendre en compte un style CSS, au travers des attributs style ou styleClass, la balise <h:outputText> génère alors, comme nous nous en doutons, une balise <span>.

Depuis la version JSF 2.0, vous n'êtes pas obligé d'utiliser la balise <h:outputText> pour afficher le contenu d'une propriété. Il est possible de s'en passer et d'écrire directement l'expression EL : #{bean.propriété}, par exemple. Vous devez toute de même utiliser cette balise dans les circonstances suivantes :

  1. Pour générer une sortie avec la prise en compte de styles CSS.
  2. Lorsque vous utilisez un panneau pour être sûr que le texte soit considéré comme une cellule à part entière.
  3. Pour générer impérativement une balise HTML.

Les balises <h:outputText> <h:outputLabel> et <h:outputFormat> possèdent un attribut très particulier qui n'existe que pour ces trois marqueurs. Il s'agit de l'attribut escape, qui par défaut prend la valeur true, et qui permet automatiquement de transformer les caractères < > et & respectivement en &lt; &gt; et &amp; afin de conserver l'affichage original. Vous devez positionner cet attribut à false si vous désirez générer des balises HTML par programme.

<h:outputFormat>

La balise <h:outputFormat> permet de composer des messages paramétrés à l'image de la classe MessageFormat. En interne, la balise <h:outputText> utilise effectivement cette classe. Vous devez ensuite spécifier vos différents paramètres à l'aide des balises <f:param>.

<h:outputFormat value="Il reste {0} jour{0, choice, 0#|2#s}" style="color: green">
<f:param
value="#{gestionEmprunts.emprunt.joursRestant}" />
</h:outputFormat>
    Dans ce code, le message utilise un seul paramètre qui détermine le nombre de jour restant avant de rendre le document emprunté dans une bibliothèque. Rappelez-vous que la numérotation du paramètre commence toujours par 0. Remarquez au passage comment faire en sorte que le pluriel soit pris en compte.
<h:outputLabel>

La balise <h:outputLabel> est la représentation exacte de l'équivalent <label> du HTML. Cette balise meconnue permet d'améliorer grandement l'ergonomie de vos formulaires. Elle permet, par exemple, de cocher un élément de type radio bouton ou de case à cocher par un clic sur le texte qui lui est joint.

De façon plus générale, la balise<h:outputLabel>permet de rattacher une information à un contrôle par le biais d'une étiquette. Comme autre exemple, elle peut être utilisé dans les formulaires pour mettre en relation une zone de saisie et son descriptif. Dans la pratique, cette relation entre<h:outputLabel>et<h:inputText>peut se faire en liant l'attribut for du LABEL à l'attribut id de l'élément INPUT.

<h:outputLabel value="Identifiant : " for="identifiant"  />
<h:inputText value="#{test.identifiant}" id="identifiant" />
<h:outputLabel value="Mot de passe : " for="passe"  />
<h:inputSecret value="#{test.motPasse}" id="passe" />
<h:selectBooleanCheckbox id="choix">
      <h:outputLabel value="Sélectionner le texte ou la case" for="choix" />
</h:selectBooleanCheckbox>
Les graphiques

Il n'existe qu'un seul composant pour afficher les images <h:graphicsImage>. Ce marqueur génère un élément <img> HTML pour afficher une image que les utilisateurs n'auront pas le droit de manipuler. Ses différents attributs permettent de modifier la taille de l'image, de l'utiliser comme image cliquable, etc. Une image peut être liée à une propriété d'un bean géré et provenir d'un fichier sur le système ou d'une base de données.

Vous pouvez spécifier la localisation de l'image avec les attributs url ou value relativement au placement de l'image dans votre application web. De plus, depuis la version 2.0 de JSF, il est souhaitable de placer vos images comme ressource dans une librairie prévue à cet effet.

<h:graphicsImage library="images" name="logo.gif"  />
<h:graphicsImage url="/resources/images/logo.gif"  />
<h:graphicsImage value="#{bean.logo}" />

Ces trois exemples réalise en réalité le même traitement, celui d'afficher le logo qui se trouve dans le sous-répertoire images qui se trouve lui-même dans le répertoire resources.

Les commandes - boutons et hyperliens

Les commandes sont les contrôles sur lesquels l'utilisateur peut cliquer pour déclencher une action. Ces composants sont généralement représentés sous forme de boutons ou de liens hypertextes spécifiés par les marqueurs suivants :

Balises Description
<h:commandButton> Représente un élément HTML pour un bouton de type submit ou reset. La méthode HTTP de la requête est de type POST.
<h:commandLink> Représente un élément HTML pour un lien agissant comme un bouton submit. Ce composant doit être placé dans un formulaire. La méthode HTTP de la requête est de type POST.
<h:button> Ce composant est similaire au marqueur <h:commandButton> mais c'est La méthode GET HTTP qui est propsée pour soumettre la requête.
<h:link> Ce composant est similaire au marqueur <h:commandLink> mais c'est La méthode GET HTTP qui est propsée pour soumettre la requête.
<h:outputLink>

Génère une balise <a> HTML. A la différence de <h:commandLink>, il n'est pas nécessaire de placer cette balise dans un formulaire, il s'agit d'un simple lien statique sans interprétation particulière pas JSF, ni d'appel de méthode spécifique du bean géré.

  1. Par défaut, un <h:commandButton> est de type submit. A l'image du HTML, nous pouvons prévoir un bouton de réinitialisation du formulaire au moyen de l'attribut type.
  2. L'attribut value permet de renseigner l'intitulé du bouton. Vous avez aussi la possibilité d'utiliser une image comme bouton. Dans ce cas ne prenez plus l'attribut value, mais plutôt l'attribut image pour indiquer le chemin d'accès du fichier image que vous souhaitez afficher.
  3. Les boutons et les liens possèdent tous les deux un attribut action qui permet soit de naviguer vers une nouvelle page, soit d'appeler une méthode spécifique du bean géré afin de réaliser un traitement particulier (qui elle-même peut proposer une navigation vers une autre page).
<h:commandButton value="Valider votre commande" action="#{bean.validation}"  />
<h:commandButton value="Réinitialiser le formulaire" type="reset" />
<h:commandButton image="image.png" action="page.xhtml" />
<h:commandLink  value="Naviguer vers votre nouvelle page" action="#{bean.méthode}" />
<h:commandLink  value="Naviguer vers votre nouvelle page" action="page.xhtml" />
  1. Les balises <h:commandButton> et <h:commandLink> sont certainement les composants principaux que nous utiliserons pour la navigation dans une application JSF, soit respectivement sous l'apparence d'un bouton ou sous l'apparence d'un hyperlien. Lorsque un bouton ou un lien est activé, une requête POST est soumise et les données du formulaire sont envoyées au serveur d'applications.
  2. JSF 2.0 introduit les composants <h:button> et <h:link>. Ces balises donnent le même rendu que les précédentes, savoir un bouton et un lien, mais lorsque nous cliquons sur un de ces composant, c'est cette fois-ci une requête GET qui est alors soumise au serveur d'applications.
  3. La balise <h:outpuLink> quant à elle génère une simple balise <a> HTML qui permet alors de pointer vers une ressource comme une image ou une autre page web. Cliquer sur ce type de lien nous renvoie directement sur la ressource désignée sans aucune interprétation particulière du framework JSF. Ce type de lien est intéressant lorsque vous désirez naviguer vers différents sites web.
Attributs Description
action

(<h:commandButton> et <h:commandLink> uniquement)
Si vous proposez une chaîne de caractères à cet attribut, c'est que vous désignez la nouvelle page web à atteindre instantanément lorsque l'utilisateur clique sur un de ces composants.
Si vous spécifiez une méthode d'un bean au travers d'une expression EL, la méthode est alors exécutée lorsque l'utilisateur clique sur un de ces composants. Si cette méthode renvoie une chaîne de caractères, elle doit alors correspondre à l'identification de la nouvelle page à atteindre.
Si vous n'utilisez pas cet attribut, c'est la page web en cours qui est réaffichée lorsque l'utilisateur clique sur un de ces composants.

outcome (<h:button> et <h:link> uniquement) indique la prochaine cible à atteindre lorsque le composant s'affichera.
fragment (<h:button> et <h:link> uniquement) Un fragment qui se rajoute à l'URL cible. Le séparateur # est automatiquement appliqué et vous n'avez pas besoin de l'inclure dans votre fragment.
actionListener Prise en compte d'un événement de type action et lance la méthode du bean proposée - méthode(ActionEvent)
image (<h:commandButton> et <h:button> uniquement) L'image proposée s'affiche à la place du rendu classique d'un bouton.
immediate La valeur attendue est de type booléen. La valeur par défaut est false. Dans ce cas là, les actions et les événements sont pris en compte uniquement à la fin du cycle de vie, c'est-à-dire après les phases de conversion et de validation. Si par contre, vous proposez la valeur true, les actions et les événements sont pris en compte immédiatement.
type Pour <h:commandButton> les types prévus sont : button, submit et reset. Par défaut, sauf si vous spécifiez l'attribut image, est submit.
Pour <h:commandLink> et <h:link> : correspond au type MIME (type de ressource), text/html, image/gif ou audio/basic par exemple.
value Intitulé du bouton ou du lien. Cela peut être une chaîne de caractères ou une expression EL.
Les sélections

Les composants de sélection permettent de choisir une ou plusieurs valeurs dans une liste. Graphiquement, ils sont représentés par des cases à cocher, des boutons radio, des listes ou des combos box.

Balises Description
<h:selectBooleanCheckbox> Génère une case à cocher représentant une valeur booléenne unique. Cette cas sera initailement cochée ou décochée selon la valeur de sa propriété checked.
<h:selectManyCheckbox> Génère une liste de cases à cocher.
<h:selectManyListBox> Génère un composant à choix multiples, dans lequel nous pouvons choisir une ou plusieurs options.
<h:selectManyMenu> Génère une balise <select> HTML.
<h:selectOneListBox>

Génère un composant à choix unique, dans lequel nous ne pouvons choisir qu'une seule option.

<h:selectOneMenu> Génère un composant à choix unique, dans lequel nous ne pouvons choisir qu'une seule option. N'affiche qu'une option à la fois.
<h:selectOneRadio> Génère une liste de boutons radio.

Les marqueurs de ce tableau ont une représentation garphique, mais ils ont besoin d'imbriquer d'autres marqueurs (<f:selectItem> ou <f:selectItems>) pour obtenir l'ensemble des valeurs disponibles.

 <h:selectOneMenu value="#{test.semaine}">
    <f:selectItem itemValue="Lundi" />
    <f:selectItem itemValue="Mardi" /> 
    <f:selectItem itemValue="Mercredi" /> 
    <f:selectItem itemValue="Jeudi" /> 
    <f:selectItem itemValue="Vendredi" />
    <f:selectItem itemValue="Samedi" /> 
    <f:selectItem itemValue="Dimanche" /> 
</h:selectOneMenu>
Attributs Description
enabledClass, disabledClass

(<h:selectOneRadio> et <h:selectManyCheckbox> uniquement) Possibilité de placer des styles CSS sur la prise en compte ou pas de cet élément.

selectedClass, unselectedClass (<h:selectManyCheckbox> uniquement) Possibilité de placer des styles CSS sur la prise en compte de la sélection ou pas de cet élément.
layout (<h:selectOneRadio> et <h:selectManyCheckbox> uniquement) Spécification qui détermine l'orientation de l'ensemble des cases à cocher ou des boutons radio lineDirection (horizontal) ou pageDirection (vertical)
label Description du composant utilisable lors d'un message d'erreur.
collectionType (<h:selectMany...> uniquement) Une chaîne de caractères ou une expression EL qui identifie une collection.
hideNoSelectionOption Cache tous les éléments qui sont désignés comme "aucune sélection" noSelectionOption dans les balise <f:selectItem>.
<h:selectBooleanCheckbox value="#{test.validation}" id="validation">
     <h:outputLabel value="Contactez-moi   " for="validation" />
</h:selectBooleanCheckbox>

@ManagedBean
public class Test {
    private boolean validation;

    public boolean isValidation() {
        return validation;
    }

    public void setValidation(boolean validation) {
        this.validation = validation;
    }
}

Cette balise représente une seule case à cocher qui doit être rattachée à une propriété booléenne.

<h:selectManyCheckbox value="#{test.couleurs}">
   <f:selectItem itemLabel="Rouge" itemValue="Color.RED"/>
   <f:selectItem itemLabel="Jaune" itemValue="Color.YELLOW" />
   <f:selectItem itemLabel="Bleu" itemValue="Color.BLUE" />
   <f:selectItem itemLabel="Vert" itemValue="Color.GREEN" />
   <f:selectItem itemLabel="Orange" itemValue="Color.ORANGE" />
</h:selectManyCheckbox>
<hr />
<h:commandButton value="Choisissez vos couleurs" />
<hr />
<h:dataTable value="#{test.couleurs}" var="couleur">
   <h:column>#{couleur}</h:column>
</h:dataTable>
<hr />

@ManagedBean
public class Test {
    private ArrayList<Color> couleurs = new ArrayList<Color>();

    public ArrayList<Color> getCouleurs() {
        return couleurs;
    }

    public void setCouleurs(ArrayList<Color> couleurs) {
        this.couleurs = couleurs;
    }
}

Vous avez la possibilité de regrouper un ensemble de cases à cocher à l'aide de cette balise. Cela implique qu'il est possible de sélectionner plusieurs éléments, et la valeur de la propriété doit correspondre à une collection.

La balise <f:selectItem> possède deux attributs. Le premier, itemValue représente la valeur de l'élément. Le deuxième, itemLabel représente l'intitulé. Vous ne l'utilisez que dans le cas où ce dernier est différent de la valeur.

<h:panelGrid columns="2" border="1" cellspacing="0">
   <h:panelGroup>
      <h:selectManyCheckbox value="#{test.couleurs}" layout="pageDirection" >
         <f:selectItem itemLabel="Rouge" itemValue="Color.RED"/>
         <f:selectItem itemLabel="Jaune" itemValue="Color.YELLOW" />
         <f:selectItem itemLabel="Bleu" itemValue="Color.BLUE" />
         <f:selectItem itemLabel="Vert" itemValue="Color.GREEN" />
         <f:selectItem itemLabel="Orange" itemValue="Color.ORANGE" />
      </h:selectManyCheckbox>
   </h:panelGroup>
   <h:panelGroup>
      <h:commandButton value="Choisissez vos couleurs" />
      <hr />
      <h:dataTable value="#{test.couleurs}" var="couleur">
         <h:column>#{couleur}</h:column>
      </h:dataTable>
   </h:panelGroup>
</h:panelGrid>

Il est possible de choisir l'orientation des cases à cocher (pour les boutons radio aussi). Par défaut, c'est un alignement horizontal qui est proposé. Vous pouvez choisir l'alignement vertical en spécifiant la valeur pageDirection dans l'attribut layout.


<h:panelGrid columns="2" border="1" cellspacing="0">
   <h:panelGroup>
       <h:selectOneRadio value="#{test.couleur}" layout="pageDirection" >
           <f:selectItem itemLabel="Rouge" itemValue="#FF0000"/>
           <f:selectItem itemLabel="Jaune" itemValue="#FFFF00" />
           <f:selectItem itemLabel="Bleu" itemValue="#0000FF" />
           <f:selectItem itemLabel="Vert" itemValue="#00FF00" />
           <f:selectItem itemLabel="Orange" itemValue="#FF6600" />
       </h:selectOneRadio>
    </h:panelGroup>
    <h:panelGroup>
       <h:commandButton value="Choisissez votre couleur" />
       <hr />
       Choix : <br />
       <h:inputTextarea value="#{test.couleur}" readonly="true"
                                  style="background: #{test.couleur}"/>
   </h:panelGroup>
</h:panelGrid>

@ManagedBean public class Test { private String couleur = "#FFFF00"; public String getCouleur() { return couleur; } public void setCouleur(String couleur) { this.couleur = couleur; } }

Il est également possible de faire un choix parmi plusieurs valeurs proposées, ce qui correspond au boutons radios. Cette fois-ci la valeur de la propriété doit représenter un élément unique.


<h:panelGrid columns="2" border="1" cellspacing="0">
   <h:panelGroup>
       <h:selectOneListbox value="#{test.couleur}" >
           <f:selectItem itemLabel="Rouge" itemValue="#FF0000"/>
           <f:selectItem itemLabel="Jaune" itemValue="#FFFF00" />
           <f:selectItem itemLabel="Bleu" itemValue="#0000FF" />
           <f:selectItem itemLabel="Vert" itemValue="#00FF00" />
           <f:selectItem itemLabel="Orange" itemValue="#FF6600" />
       </h:selectOneListbox>
    </h:panelGroup>
    <h:panelGroup>
       <h:commandButton value="Choisissez votre couleur" />
       <hr />
       <h:inputText value="#{test.couleur}" readonly="true"
                           style="background: #{test.couleur}" size="23"/>
   </h:panelGroup>
</h:panelGrid>

Une autre solution pour faire un choix parmi plusieurs valeurs proposées, c'est d'utiliser une liste. Là aussi, la valeur de la propriété doit représenter un élément unique.

Par défaut, tous les éléments de la liste sont systématiquement visibles. Vous avez la possibilté de limiter le nombre d'éléments visibles au moyen de l'attribut size. Un ascenceur vertical sera alors proposé.

<h:panelGrid columns="2" border="1" cellspacing="0">
   <h:panelGroup>
      <h:selectManyListbox value="#{test.couleurs}" layout="pageDirection" >
         <f:selectItem itemLabel="Rouge" itemValue="Color.RED"/>
         <f:selectItem itemLabel="Jaune" itemValue="Color.YELLOW" />
         <f:selectItem itemLabel="Bleu" itemValue="Color.BLUE" />
         <f:selectItem itemLabel="Vert" itemValue="Color.GREEN" />
         <f:selectItem itemLabel="Orange" itemValue="Color.ORANGE" />
      </h:selectManyListbox>
   </h:panelGroup>
   <h:panelGroup>
      <h:commandButton value="Choisissez vos couleurs" />
      <hr />
      <h:dataTable value="#{test.couleurs}" var="couleur">
         <h:column>#{couleur}</h:column>
      </h:dataTable>
   </h:panelGroup>
</h:panelGrid>

@ManagedBean
public class Test {
    private ArrayList<Color> couleurs = new ArrayList<Color>();

    public ArrayList<Color> getCouleurs() {
        return couleurs;
    }

    public void setCouleurs(ArrayList<Color> couleurs) {
        this.couleurs = couleurs;
    }
}

Grâce à cette balise, vous pouvez aussi prendre en compte plusieurs valeurs dans une liste. Attention, il faut que la propriété qui gère l'ensemble des valeurs choisies soit une collection.

<h:panelGrid columns="2" border="1" cellspacing="0">
   <h:panelGroup>
       <h:selectOneMenu value="#{test.couleur}" >
           <f:selectItem itemLabel="Rouge" itemValue="#FF0000"/>
           <f:selectItem itemLabel="Jaune" itemValue="#FFFF00" />
           <f:selectItem itemLabel="Bleu" itemValue="#0000FF" />
           <f:selectItem itemLabel="Vert" itemValue="#00FF00" />
           <f:selectItem itemLabel="Orange" itemValue="#FF6600" />
       </h:selectOneMenu>
    </h:panelGroup>
    <h:panelGroup>
       <h:commandButton value="Choisissez votre couleur" />
       <hr />
       <h:inputText value="#{test.couleur}" readonly="true"
                           style="background: #{test.couleur}" size="23"/>
   </h:panelGroup>
</h:panelGrid>

Une dernière solution pour faire un choix parmi plusieurs éléments est la liste déroulante, souvent appelé menu dans les applications web.

La balise <f:selectItem>

Dans tous les exemples que nous venons de voir, la balise <f:selectItem> représente un seul élément de l'ensemble de la sélection. Pour connaître tous les choix possibles, vous devez donc proposer un ensemble de balise <f:selectItem> imbriqués à l'intérieur du type de réprésentation que vous désirez obtenir.

Dans l'exemple ci-dessus, une des valeurs : #FF0000, #FFFF00, #0000FF, etc. va être transmise à la propriété couleur du bean test, lorsque le choix est effectué et lorsque l'ordre de soumission a été envoyé.

L'attribut itemValue représente une de ces valeurs. Si cette valeur est celle que vous désirez visualiser dans le rendu de la page web, vous n'avez pas besoin d'autre attribut. Si par contre, vous souhaitez proposer un intitulé différent de la valeur à envoyer, vous pouvez alors utiliser l'attribut itemLabel.

Avec la valeur et son étiquette, dans un élément de sélection, vous pouvez aussi lui proposer une description ou même le désigner temporairement inactif. Par ailleurs, avec JSF 2.0, il existe un attribut supplémentaire noSelectionOption qui permet de désigner l'élément comme une invitation à choisir une sélection et non comme une valeur à prendre en compte. Cet attribut s'utilise en conjonction avec la phase de validation. Dans le cas d'un non choix de la par de l'utilisateur, un message d'erreur peut alors être proposé.

<h:panelGrid columns="1" style="background: darkgreen" cellpadding="3">
   <h:panelGroup>
       <h:selectOneMenu value="#{test.couleur}"  id="choix" required="true"
                                     requiredMessage="Faites votre choix !">
           <f:selectItem itemLabel="Choisissez votre couleur" noSelectionOption="true" />
           <f:selectItem itemLabel="Rouge" itemValue="#FF0000" itemDisabled="true"/>
           <f:selectItem itemLabel="Jaune" itemValue="#FFFF00" />
           <f:selectItem itemLabel="Bleu" itemValue="#0000FF" />
           <f:selectItem itemLabel="Vert" itemValue="#00FF00" />
           <f:selectItem itemLabel="Orange" itemValue="#FF6600" />
        </h:selectOneMenu>
        <h:commandButton value="Ok" />
     </h:panelGroup>
     <h:message for="choix" />
     <h:inputText value="#{test.couleur}" readonly="true"
                         style="background: #{test.couleur}" size="30" />
</h:panelGrid>

Attributs Description
itemDescription

Description de l'élément utile uniquement avec un outil adapté.

itemDisabled Permet de rendre temporairement un élément inactif.
itemLabel Texte envoyé pour le rendu de l'élément (partie visuelle).
itemValue Valeur prise en compte par la requête soumise au serveur d'applications.
escape Permet d'intégrer des caractères spéciaux qui ne doivent pas être interpréter par JSF.
value Expression EL qui identifie un objet de type SelectItem.
<f:selectItem value="#{test.couleurRouge}" />

SelectItem getCouleurRouge() {  return new SelectItem("Rouge", "#FF0000");  }

// Constructeurs possibles
SelectItem(Object value)
SelectItem(Object value, String label)
SelectItem(Object value, String label, String description)
SelectItem(Object value, String label, String description, boolean disabled)
SelectItem(Object value, String label, String description, boolean disabled, boolean escape)
SelectItem(Object value, String label, String description, boolean disabled,  boolean escape, boolean noSelectionOption)
noSelectionOption Proposer la valeur true si cet élément n'est pas un choix possible dans la liste de sélection.
La balise <f:selectItems>

La balise <f:selectItem> est polyvalente, mais cela peut s'avérer rapidement fastidieux d'écrire systématiquement l'ensemble des choix possibles sur la vue. Nous pouvons réduire considérablement le code en proposant plutôt la balise <f:selectItems>. Dans ce cas, c'est le modèle qui doit prendre le relais et se préoccuper de spécifier l'ensemble des éléments que comporte la sélection.

La vue
<h:body style="background-color: green; color: yellow; font-weight: bold">
  <h:form>
     <h:panelGrid columns="1" style="background: darkgreen" cellpadding="3">
        <h:panelGroup>
           <h:selectOneMenu value="#{test.couleur}"  id="choix" required="true" requiredMessage="Faites votre choix !">
                <f:selectItems value="#{test.couleurs}" />
           </h:selectOneMenu>
           <h:commandButton value="Ok" />
        </h:panelGroup>
        <h:message for="choix" />
        <h:inputText value="#{test.couleur}" readonly="true"  style="background: #{test.couleur}" size="30" />
     </h:panelGrid>
   </h:form>
</h:body>
Le modèle
import javax.faces.bean.*;
import javax.faces.model.SelectItem;

@ManagedBean
public class Test {
    private String couleur;
    private SelectItem[] couleurs = {
        new SelectItem(null, "Choisissez votre couleur", "", false, false, true),
        new SelectItem("#FF0000", "Rouge", "", true),
        new SelectItem("#FFFF00", "Jaune"),
        new SelectItem("#0000FF", "Bleu"),
        new SelectItem("#00FF00", "Vert"),
        new SelectItem("#FF6600", "Orange")
    };

    public String getCouleur() { return couleur;  }
    public void setCouleur(String couleur) { this.couleur = couleur;  }
    public SelectItem[] getCouleurs() { return couleurs;  }   
}  

L'attribut value de la balise <f:selectItems> doit posséder une expression EL qui représente l'un des quatre points suivants : une instance d'un seul SelectItem, une collection, un tableau ou une carte où chaque entrée représente une étiquette suivie de sa valeur.

Une balise <f:selectitems> est très souvent plus facile à manipuler que la balise <f:selectItem>. Dans le cas où le nombre d'éléments varie, avec <f:selectItems>, généralement vous avez besoin de changer uniquement que le modèle, alors qu'avec la balise <f:selectItem>, vous devez modifier à la fois la vue et le modèle.

Attributs Description
value Expression EL qui représente un seul élément, une collection, un tableau ou une carte d'entrées.
var Nom de la variable qui va être utilisée pour récupérer les propriétés d'un élément de la collection pour renseigner en une seule fois son intitulé et sa valeur, respectivement exprimés au travers des attributs itemLabel et itemValue.
itemLabel Intitulé de l'élément associé à l'attribut var.
itemValue Valeur de l'élément associé à l'attribut var.
itemDescription Description de l'élément associé à l'attribut var.
itemDisabled Rendre l'élément associé à l'attribut var momentanément inactif.
itemLabelEscape Permettre à l'élément associé à l'attribut var de ne pas interpréter le code interne.
noSelectionOption Elément associé à l'attribut var qui ne fait pas parti de la sélection.

Avant JSF 2.0, les collections et les tableaux devaient contenir impérativement des instances de type SelectItem, comme l'exemple proposé plus haut. Nous remarquons tout de suite que là aussi cela peut être assez fastidieux. Depuis lors, il est maintenant possible de proposer une collection ou un tableau de n'importe quel type d'objets, ce qui est largement plus intéressant.

Si vous utilisez une classe quelconque comme élément de votre collection, les intitulés de vos sélections sont générés automatiquement à partir de la méthode toString() de votre classe. A vous de redéfinir cette méthode pour que tout se fasse automatiquement.

<h:selectOneMenu value="#{test.jour}">
    <f:selectItems value="#{test.jours}" />
</h:selectOneMenu>

@ManagedBean
public class Test {
    private enum JourSemaine {Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi, Dimanche};
    private JourSemaine[] jours = JourSemaine.values();
    private JourSemaine jour;

    public JourSemaine[] getJours() { return jours;  }
    public JourSemaine getJour() { return jour;  }
    public void setJour(JourSemaine jour) { this.jour = jour;  }
}      

D'une autre façon, vous pouvez utiliser l'attribut var pour définir une variable qui représente chaque élément particulier de la collection. Nous pouvons faire correspondre à cette variable, l'intitulé ainsi que la valeur associée, en prenant en compte les propriétés liées à l'élément, en spécifiant ainsi les attributs itemLabel et itemValue.

Dans le même ordre d'idée, vous pouvez définir l'élément qui portera une description, qui sera momentanément inactif ou qui ne fera pas parti de la sélection au moyen des attributs respectifs : itemDescription, itemDisabled et noSelectionOption.

<style type="text/css">
        body { background-color: green; color: yellow; }
        .fond, .droite { background: yellow; font-weight: bold; }
         .droite { text-align: right; }
</style>

<h:body>
   <h:form>
       <h:panelGrid columns="2" style="background: darkgreen" cellpadding="3">
           <h:selectOneMenu  value="#{test.nombreJours}" onchange="submit()" styleClass="fond">
               <f:selectItems value="#{test.mois}" var="mois" itemValue="#{mois.numéroMois}" />
           </h:selectOneMenu>
           <h:inputText value="#{test.nombreJours}" readonly="true" size="3" styleClass="droite"/>
        </h:panelGrid>
    </h:form>
</h:body>

@ManagedBean
public class Test {
    public enum Mois {
        Janvier, Février, Mars, Avril, Mai, Juin, Juillet, Août, 
        Septembre, Octobre, Novembre, Décembre;
        
        public int getNuméroMois() { return ordinal()+1; }
    };   
    
    private Mois[] mois = Mois.values();
    private int nombreJours;

    public Mois[] getMois() { return mois;  }
    
    public int getNombreJours() { return nombreJours; }
    public void setNombreJours(int nombreJours) {
        this.nombreJours = nombreJours;
    }   
}
Liaison entre l'attribut value et la propriété du bean géré

Lorsque vous utilisez un ensemble de cases à cocher, des menus ou des listes, vous avez toujours besoin de récupérer l'élément sélectionné par rapport à l'ensemble proposé. Comme nous venons de le voir, vous devez alors utiliser l'attribut value associée à une propriété du bean géré au travers d'une expression EL. Cette propriété correspond ainsi à la valeur délivrée par l'attribut itemValue de l'élément.

ATTENTION, lorsque la requête est soumise, le serveur d'application reçoit la chaîne sélectionnée (protocole HTTP) et doit le convertir dans le type approprié. L'implémentation JSF sait comment convertir un nombre ou une énumération, mais vous êtes obligé de prévoir un convertisseur dans tous les autres cas.

Les messages d'erreur

Les beans gérés traitent de la logique métier, appellent les EJB, utilisent les bases de données, etc. Parfois, cependant, un problème peut survenir et, en ce cas, l'utilisateur doit être informé par un message qui peut être un message d'erreur de l'application (concernant la logique métier ou la connexion à la base ou au réseau) ou un message d'erreur de saisie (un ISBN incorrect ou un champ vide par exemple).

Les erreurs d'application peuvent produire une page particulière demandant à l'utilisateur de réessayer dans un moment, par exemple, alors que les erreurs de saisie peuvent s'afficher dans la même page avec un texte décrivant l'erreur. Nous pouvons également utiliser des messages pour informer l'utilisateur qu'un livre a été correctement ajouté à la base de données.

Typiquement, pour les erreurs de saisie, les messages sont associés avec un composant particulier et précise qu'une erreur de conversion ou de validation est survenue. Bien que nous utilisons les messages souvent de la même façon, il existe en réalité quatre variétés de message : Information, Avertissement, Erreur et Fatal.

Tous les types de message comporte une description sommaire et une description détaillée. Par exemple, de façon sommaire nous pourrions dire que l'entrée est invalide, alors que dans le détail nous pourrions indiquer que le nombre saisi est plus grand que la valeur maximal prévue. Les applications JSF utilise deux balises pour représenter ces messages : <h:messages> et <h:message>.

  1. <h:messages> : Cette balise affiche tous les messages d'erreur survenant dans l'ensemble de la page.
  2. <h:message> : Cette balise affiche un seul message d'erreur associé à un composant en particlier. Ce composant est spécifié au travers de l'attribut for, lui-même devra proposer un identifiant.
Attributs Description
errorClass, errorStyle, fatalClass, fatalStyle, infoClass, infoStyle, warnClass, warnStyle Styles CSS associés aux quatre types de message d'erreur.
for Identifiant désignant le composant à prendre en compte pour afficher un éventuel message d'erreur le concernant. (<h:message> uniquement).
globalOnly Instruction imposant d'afficher uniquement les messages d'erreur globaux, les messages particuliers étant alors désactivés. (<h:messages> uniquement).
layout Permet de spécifier l'orientation de l'ensemble des messages d'erreur survenant : "table" ou "list". (<h:messages> uniquement).
showDetail Booléen qui détermine si le message d'erreur doit être affiché sous forme détaillé. Par défaut, les valeurs sont false pour <h:messages> et true pour <h:message>.
showSummary Booléen qui détermine si le message d'erreur doit être affiché sous forme simplifiée. Par défaut, les valeurs sont true pour <h:messages> et false pour <h:message>.
tooltip Booléen qui détermine si un message détaillé doit être rendu visible dans une petite bulle d'avertissement. Cet avertissement ne sera toutefois visible uniquement à la condition que les attributs showDetail et showSummary soient validés.
 
<style type="text/css">
    body { background-color: green; color: yellow; font-weight: bold; }
    .cadre { background: darkgreen; }
    .erreur { color: chartreuse; font-style: italic; text-align: left; }
</style>

<h:body>
   <h:form>
       <h:panelGrid columns="3" styleClass="cadre">
           <f:facet name="header">
                <h:messages styleClass="erreur"/>
           </f:facet>
            Nom :
           <h:inputText id="nom" value="#{test.nom}" required="true" label="Nom"/>
           <h:message for="nom" errorClass="erreur" />
            Âge :
            <h:inputText id="âge" value="#{test.âge}" size="3" label="Âge" />
            <h:message for="âge"  errorClass="erreur" />
            <f:facet name="footer">
                <hr />
                <h:commandButton value="Soumettre" />
            </f:facet>
        </h:panelGrid>            
    </h:form>
</h:body>

Les messages d'erreurs proposées sont automatiquement traduite dans la langue locale. Ils sont relativement adaptés à la situation. Il est également possible de proposer un message personnalisé sur chaque balise de saisie au moyen des attributs converterMessage, validatorMessage et requiredMessage.

 
<style type="text/css">
    body { background-color: green; color: yellow; font-weight: bold; }
    .cadre { background: darkgreen; }
    .erreur { color: chartreuse; font-style: italic; text-align: left; }
</style>

<h:body>
   <h:form>
       <h:panelGrid columns="3" styleClass="cadre">
            Nom :
           <h:inputText id="nom" value="#{test.nom}" required="true" requiredMessage="Erreur : indispensable" />
           <h:message for="nom" errorClass="erreur" />
            Âge :
            <h:inputText id="âge" value="#{test.âge}" size="3" converterMessage="Erreur : valeur entre 0 et 120" />
            <h:message for="âge"  errorClass="erreur" />
            <f:facet name="footer">
                <hr />
                <h:commandButton value="Soumettre" />
            </f:facet>
        </h:panelGrid>            
    </h:form>
</h:body>

 

Choix du chapitre Les données sous forme de tableau dynamique

Les données doivent souvent être affichées sous forme de tableau. JSF fournit donc le marqueur <h:dataTable> permettant ainsi de parcourir une liste d'éléments (dont la taille est variable) afin de générer automatiquement un tableau. Typiquement, un tableau dynamique de données possède la structure suivante :

<h:dataTable value="#{éléments}" var="élément">
   <h:column>
       #{élément.premièrePropriété}
   </h:column>
   <h:column>
       #{élément.deuxièmePropriété}
   </h:column>
...
</h:dataTable>

L'attribut value représente l'ensemble des données que pourra parcourir par itération la balise <h:dataTable>. Les données que peut exploiter l'attribut value est de l'une des natures suivantes : un simple objet Java, un tableau, une instance de java.util.List, une instance de java.sql.ResultSet, une instance de javax.faces.model.DataModel.

  1. La balise <h:dataTable> prend en compte l'ensemble des éléments constituant la collection choisie. Du coup, la longueur du tableau est totalement variable et dépend du contenu en cours de la collection. Il s'agit donc bien d'un tableau dynamique qui affiche les données actuellement accessibles.
  2. Chaque élément de la collection est représenté par une ligne de la table. Il est possible d'identifier l'élément de la ligne courante en spécifiant l'attribut var de la balise <h:dataTable>. Cet élément représente généralement un simple objet avec lequel nous pouvons ensuite manipuler chaque propriété séparément, représentée dans une colonne distincte.
  3. Plus rarement, au lien de proposer une collection, nous pouvons spécifier un seul objet Java dans l'attribut value de <h:dataTable>, auquel cas une seule ligne apparaîtra puiqu'il n'existe qu'une seule occurrence.
  4. Le corps de la balise <h:dataTable> ne peut contenir uniquement que des balises <h:column>. La balise <h:dataTable> ignore toutes les autres balises que vous placerez. Par contre, chaque colonne peut contenir un nombre indéfini de composants internes.
La balise <h:dataTable> est en relation avec un composant de type UIData qui s'occupe du rendu de la table. Cette combinaison permet de générer des tables robustes qui supportent les styles CSS, l'accès aux bases de données, la personnalisation des modèles de table, et bien d'autres. Nous allons commencer notre exploration de ce marqueur <h:dataTable> en élaborant tout d'abord une simple table.
<style>
    body   { background-color: green; color: yellow; font-weight: bold; }
    .cadre { background: darkgreen; border-radius: 5px; padding: 7px; 
                 box-shadow: -1px -1px 3px lightgreen, 1px 1px 2px black; }
    .saisie { background: yellow; color: darkgreen; text-align: right;
                 padding-right: 5px; font-weight: bold; }
</style>

<h:body>
    <h:form>
        <h:panelGrid columns="2" styleClass="cadre">
            <h:outputText value="Prénom : " />
            <h:inputText value="#{gestion.personne.prénom}" styleClass="saisie"/>
            <h:outputText value="Nom : " />
            <h:inputText value="#{gestion.personne.nom}" styleClass="saisie" />
            <f:facet name="footer">
                <hr />
                <h:commandButton value="Nouveau" action="#{gestion.nouveau()}"/>
                <h:commandButton value="Enregistrer" action="#{gestion.ajout()}" />
            </f:facet>
        </h:panelGrid>     
        <br />
        <h:dataTable value="#{gestion.personnes}" var="personne" 
                             styleClass="cadre" rendered="#{gestion.pasVide}">
            <h:column>#{personne.prénom}</h:column>
            <h:column>#{personne.nom}</h:column>
        </h:dataTable>
    </h:form>
</h:body>

@ManagedBean
@SessionScoped
public class Gestion {
    private Personne personne = new Personne();
    private ArrayList<Personne> personnes = new ArrayList<Personne>();

    public Personne getPersonne() { return personne; }
    public void setPersonne(Personne personne) {  this.personne = personne;  }

    public ArrayList<Personne> getPersonnes() {  return personnes;   }
    
    public void ajout() { personnes.add(personne); }
    public void nouveau() { personne = new Personne(); }
    public boolean isPasVide() { return !personnes.isEmpty(); }
}

public class Personne {
    private String nom; 
    private String prénom;

    public String getNom() { return nom;  }
    public String getPrénom() { return prénom;   }
    
    public void setNom(String nom) {
        this.nom = nom.toUpperCase();
    }

    public void setPrénom(String prénom) {
       if (prénom.isEmpty()) return;
       StringBuilder chaine = new StringBuilder(prénom.toLowerCase());
       chaine.setCharAt(0, Character.toUpperCase(chaine.charAt(0)));
       this.prénom = chaine.toString();
    }
}

Dans l'exemple ci-dessus, le bean géré gestion permet de gérer l'ensemble des personnes à enregistrer et possède donc une collection personnes qui est donc rattachée à l'attribut value de la balise <h:dataTable>. Ensuite, grâce à l'attribut var de cette balise, nous pouvons visulaiser chaque personne en particulier sur chacune des lignes du tableau. A l'intérieur de ce tableau, nous rajoutons deux balises <h:column> qui vont nous permettre de visualiser ainsi séparément, sous formes de colonnes, le nom et le prénom de chaque personne.

Attributs Description des attributs de la balise <h:dataTable>
bgcolor Couleur de fond de la table.
border Largeur de la bordure de la table.
captionClass Style CSS associé au titre de la table.
cellpadding Espacement minimum à l'intérieur de chaque cellule.
cellspacing Espacement entre les cellules.
columnClasses Liste des styles CSS pour les colonnes de la table.
dir Direction du texte : LTR (Left to Right) ou RTL (Right to Left).
first Index de la collection à partir duquel nous commençons à afficher la première ligne du tableau.
footerClass Style CSS associé au pied du tableau.
frame Spécification pour déterminer le comportement du cadre par rapport à son environnement. Valeurs attendues : none, above, below, hsides, vsides, lhs, rhs, box, border.
headerClass Style CSS pour spécialement prévue pour la partie en-tête.
rowClasses Liste des styles CSS pour les lignes de la table.
rows Nombre de lignes affichable dans la table. Généralement en relation avec l'attribut first. Si vous proposez la valeur 0, toute les lignes de la table vont être affichées.
rules Règles spécifique aux tracés des lignes entre les cellules. Valeurs possibles : groups, rows, columns, all.
summary Sommaire de la table non visible, pour une utilisation en coulisse.
var Nom proposé qui représente la ligne courante, une valeur spécifique de l'ensemble de la collection.
Attributs Description des attributs de la balise <h:columns>
footerClass Style CSS associé au pied du tableau.
headerClass Style CSS pour spécialement prévue pour la partie en-tête.
En-tête, pied et titre sur une table

Si nous affichons la liste des noms et prénoms comme nous venons de le faire dans le projet précédent, ce n'est pas très agéable car il n'est par toujours facile de s'y retrouver entre le nom et le prénom d'une personne. Il serait souhaitable de proposer au moins un titre à chacune des colonnes visibles.

A l'image de la balise <h:panelGrid>, nous avons la possiblité de rajouter des facettes pour spécifier une partie en-tête, un pied sur chacune des colonnes et un titre sur l'ensemble de la table.

<style>
    body { background-color: green; color: yellow; font-weight: bold; }
    .cadre { background: darkgreen; border-radius: 5px; padding: 7px; 
                 box-shadow: -1px -1px 3px lightgreen, 2px 2px 5px black;  }
    .saisie { background: yellow; color: darkgreen; text-align: right; padding-right: 5px;
                 font-weight: bold; }
    .en-tete { background: lightgreen; color: darkgreen;  }
    .colonne { padding: 4px; width: 124px; }
</style>

<h:body>
    <h:form>
        <h:panelGrid columns="2" styleClass="cadre" >
            <h:outputText value="Prénom : " />
            <h:inputText value="#{gestion.personne.prénom}" styleClass="saisie"/>
            <h:outputText value="Nom : " />
            <h:inputText value="#{gestion.personne.nom}" styleClass="saisie" />
            <f:facet name="footer">
                <hr />
                <h:commandButton value="Nouveau" action="#{gestion.nouveau()}"/>
                <h:commandButton value="Enregistrer" action="#{gestion.ajout()}" />
            </f:facet>
        </h:panelGrid>     
        <h:dataTable value="#{gestion.personnes}" var="personne" styleClass="cadre"
                  headerClass="en-tete" captionClass="cadre" 
                  columnClasses="colonne, colonne" rules="all" >
            <f:facet name="caption">Liste des personnes</f:facet>
            <h:column>
                <f:facet name="header">Prénom</f:facet>
                 #{personne.prénom}
            </h:column>
            <h:column>
                <f:facet name="header">Nom</f:facet>
                #{personne.nom}
            </h:column>
        </h:dataTable>
    </h:form>
</h:body>
Composants dans une table et mode d'édition

Jusqu'à présent, nous n'avons placé uniquement des composants pour afficher du texte. En réalité, vous pouvez introduire une très grande variété de composants graphiques, des zones de saisie, des boutons, des images, des menus, des cases à cocher, des boutons radio, des listes, etc.

En prévoyant tous ces types de composant, il devient possible d'avoir des tables éditables, pour saisir de nouvelles entrées, modifier celles qui sont déjà présentes, supprimer des lignes, etc.


<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <style>
        body { background-color: green; color: yellow; font-weight: bold; }
        .cadre { background: darkgreen; border-radius: 5px; padding: 7px; 
                       box-shadow: -1px -1px 3px lightgreen, 2px 2px 5px black;  }
        input[type="text"] { background: yellow; color: darkgreen; font-weight: bold; }
        .en-tete { background: green; padding-left: 10px; padding-right: 10px; 
                         border-radius: 3px; text-shadow: 2px 2px 7px black; }
    </style>

    <h:body>
        <h:form>   
            <h:dataTable value="#{gestion.personnes}" var="personne" styleClass="cadre" columnClasses="en-tete">              
                <h:column headerClass="en-tete">
                    <f:facet name="header">Marié</f:facet>
                    <h:selectBooleanCheckbox value="#{personne.marié}" />
                </h:column>
                <h:column headerClass="en-tete">
                    <f:facet name="header">Prénom</f:facet>
                    <h:inputText value="#{personne.prénom}" />
                </h:column>
                <h:column headerClass="en-tete">
                    <f:facet name="header">Nom</f:facet>
                    <h:inputText value="#{personne.nom}" />
                </h:column>
                <h:column><h:commandButton value="Enregistrer" /></h:column>
                <h:column>
                    <h:commandButton value="Supprimer" action="#{gestion.supprimer(personne)}" />
                </h:column>
                <f:facet name="footer">
                    <hr />
                    <h:commandButton value="Ajouter" action="#{gestion.ajout()}" />
                </f:facet>
            </h:dataTable>
        </h:form>
    </h:body>
</html>

@ManagedBean
@SessionScoped
public class Gestion {
    private ArrayList<Personne> personnes = new ArrayList<Personne>();
    public ArrayList<Personne> getPersonnes() {  return personnes;   }
    
    @PostConstruct
    public void ajout() { personnes.add(new Personne());  }
    
    public void supprimer(Personne personne) { 
        personnes.remove(personne);  
        if (personnes.isEmpty()) personnes.add(new Personne());
    }
}  

Dans cet exemple, nous avons placés des composants différents dans l'ensemble des colonnes, des cases à cocher, des zones de saisie et pour finir des boutons de commande.

Vous remarquez qu'il est même possible de supprimer une ligne. Il suffit de faire appel à la méthode qui permet cette suppression en passant en paramètre l'objet correspondant à la ligne courante, celui qui est donc déclaré par l'attribut var de la balise <h:dataTable>. Pour ajouter une nouvelle ligne, il suffit de rajouter un nouvel objet vierge à votre collection et de rafraîchir la page, et le tour est joué.

 

Choix du chapitre Projet de conclusion

Pour conclure cette première partie sur JSF, je vous propose de mettre en oeuvre un dernier projet qui valide les différents concepts que nous venons de découvrir. La prochaine étude nous permettra d'aller un peu plus loin dans le domaine. Nous verrons notamment comment :

  1. Réaliser des modèles de page,
  2. Créer de nouveau composants,
  3. Construire des convertisseurs et des validateurs personnalisés,
  4. S'occuper de toute la partie événementielle,
  5. Intégrer des modules sous Ajax,
  6. etc.

Le projet que je vous propose permet de réaliser une série de conversions monétaires. L'ensemble des conversions sont systématiquement enregistrées dans une base de données. Elles sont accessibles dans un tableau limité à quatre conversions visibles en même temps. Vous pouvez par contre naviguer dans l'ensemble des pages.

Voici maintenant l'ensemble des éléments qui constitue le projet, respectivement :
  1. L'entité Conversion qui rend persistant l'ensemble des conversions,
  2. Le bean géré CDI Gérer qui est également un bean session qui sert de modèle pour l'application web et qui s'occupe de la persistance de l'entité Conversion.
  3. La feuille de style principal.css utile pour la vue,
  4. La page unique du site index.xhtml qui sert de vue à l'application web
L'entité entité.Conversion
package entité;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@NamedQuery(name="toutesLesConversions", query="SELECT conversion FROM Conversion conversion")
public class Conversion implements Serializable {
    @Id   @GeneratedValue
    private long id;
    private double euro;
    private double franc;
    private char sens;

    public long getId() { return id;  }
    public double getEuro() { return euro;  }
    public double getFranc() {  return franc;  }
    public char getSens() { return sens; }
    public void setSens(char sens) {  this.sens = sens;  }

    public Conversion(char sens, double euro, double franc) {
        this.sens = sens;
        this.euro = euro;
        this.franc = franc;
    }
    
    public Conversion() {  }
}
Le bean session stateful et CDI bean.Gérer
package bean;

import entité.Conversion;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.Stateful;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import javax.persistence.*;

@Named
@SessionScoped
@Stateful
public class Gérer {
    @PersistenceContext
    private EntityManager bd;
    private List<Conversion> conversions;
    private final double TAUX = 6.55957;
    private double euro;
    private double franc;
    private char sens = 'F';
    private int pages;
    private int page;

    public double getEuro() {  return euro;  }
    public void setEuro(double euro) {  this.euro = euro;  }

    public double getFranc() { return franc;  }
    public void setFranc(double franc) { this.franc = franc;  }

    public char getSens() { return sens;  }
    public void setSens(char sens) {  this.sens = sens;  }
    
    public List<Conversion> getConversions() { return conversions;  }

    public int getPages() { return pages;  }   
    public int getPage() { return page;  }   
    
    @PostConstruct
    public void liste() {
        Query requête = bd.createNamedQuery("toutesLesConversions");
        conversions = requête.getResultList();
        pages = ((conversions.size() - 1) / 4) + 1;
    }   
    
    public void convertir() {
        switch (sens) {
            case '€' : euro = franc / TAUX; break; 
            case 'F' : franc = euro * TAUX; break;     
        }
        bd.persist(new Conversion(sens, euro, franc));
        liste();
    }
    
    public void supprimer(Conversion conversion) {
        bd.remove(bd.find(Conversion.class, conversion.getId())); 
        liste();
    }  
    
    public void changerPage(int décalage) {
        page += décalage;
    }
}  
La feuille de style web/css/principal.css
root { 
    display: block;
}

body { 
    background-color: green; 
    color: yellow; 
    font-weight: bold; 
}

.cadre { 
    background: darkgreen; 
    border-radius: 5px; 
    padding: 7px; 
    box-shadow: -1px -1px 3px lightgreen, 2px 2px 5px black;  
}

.saisie, .resultat, .pages { 
    background: yellow;
    color: darkgreen; 
    font-weight: bold; 
    text-align: right; 
    padding-right: 7px; 
    padding-left: 7px;
}

.saisie {
    margin-right: 15px;  
    margin-left: 15px;
}

.resultat, .pages { 
    border-radius: 5px; 
    box-shadow: 1px 1px 3px black inset; 
    margin-left: 20px;
}

.resultat { 
    width: 170px; 
    display: block;  
}

.fond { 
    background: black; 
    padding: 6px;
    border-radius: 5px;   
    opacity: 0.9; 
}  
La vue index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
    
    <h:head />
    
    <h:outputStylesheet name="principal.css" library="css" />

    <h:body>
        <h:form>   
            <h:dataTable value="#{gérer.conversions}" 
                                  var="conversion" 
                                  styleClass="cadre" 
                                  footerClass="fond" 
                                  headerClass="fond"
                                  rows="4"
                                  first="#{gérer.page * 4}">       
                
                <f:facet name="header">
                    <h:inputText value="#{gérer.euro}" styleClass="saisie">
                        <f:convertNumber type="currency" currencySymbol="Euros"/>
                    </h:inputText>               
                     <h:selectOneMenu value="#{gérer.sens}">
                        <f:selectItem itemLabel="en Euro" itemValue="€" /> 
                        <f:selectItem itemLabel="en Franc" itemValue="F" />
                    </h:selectOneMenu>
                    <h:inputText value="#{gérer.franc}" styleClass="saisie">
                        <f:convertNumber type="currency" currencySymbol="Francs"/>
                    </h:inputText>   
                     <h:commandButton value="Convertir" action="#{gérer.convertir()}" />                    
                </f:facet>               
                <h:column headerClass="en-tete">
                    <h:outputText value="#{conversion.euro}" styleClass="resultat">
                        <f:convertNumber type="currency" currencySymbol="Euros" />
                    </h:outputText>
                </h:column>
                <h:column headerClass="en-tete">
                    <h:selectOneRadio value="#{conversion.sens}">
                        <f:selectItem itemLabel="€" itemValue="€" /> 
                        <f:selectItem itemLabel="F" itemValue="F" />
                    </h:selectOneRadio>
                </h:column>
                <h:column headerClass="en-tete">
                    <h:outputText value="#{conversion.franc}" styleClass="resultat">
                        <f:convertNumber type="currency" currencySymbol="Francs" />
                    </h:outputText>
                </h:column>
                <h:column>
                </h:column>
                <h:column>
                    <h:commandButton value="Supprimer" action="#{gérer.supprimer(conversion)}" />
                </h:column>
                <f:facet name="footer">     
                    <h:outputFormat value="{0} page{0, choice, 0#|2#s}"  styleClass="pages">
                        <f:param value="#{gérer.pages}" />
                    </h:outputFormat>                        
                    <h:commandButton value="Page précédente" disabled="#{gérer.page == 0}" action="#{gérer.changerPage(-1)}"/>
                    <h:commandButton value="Page suivante"  disabled="#{gérer.page == gérer.pages-1}"  action="#{gérer.changerPage(1)}" />
                    <h:outputFormat value="{0} page{0, choice, 0#|2#s}"  styleClass="pages">
                        <f:param value="#{gérer.pages}" />
                    </h:outputFormat>                       
                </f:facet>
            </h:dataTable>
        </h:form>
    </h:body>
</html>