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).
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).
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.
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 />.
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.
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.
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.
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.
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 :
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.
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.
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.
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 :
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.
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).
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).
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 :
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.
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.
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. |
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.
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 :
package bean; import javax.faces.bean.*; @ManagedBean(name= ) @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; } }
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. |
<?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= xmlns:h= xmlns:f=> <h:head> <title><h:outputText value= /></title> </h:head> <h:outputStylesheet library= name= /> <h:body> <h3><h:outputText value= /></h3> <hr /> <h:form> <h:inputText value=#{conv.euro} styleClass=saisie id=euro converterMessage= validatorMessage= required= requiredMessage=> <f:convertNumber type= currencySymbol=/> <f:validateDoubleRange minimum= /> </h:inputText> <h:commandButton value= action=#{conv.euroFranc} /> <h:inputText value=#{conv.franc} readonly= styleClass=resultat> <f:convertNumber type= currencySymbol=/> </h:inputText> <p><h:message for= styleClass=erreur/></p> </h:form> </h:body> </html>
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 :
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 :
Pour conclure sur cette première approche, un bean géré doit respecter les contraintes suivantes :
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.
package bean; import javax.io.Serializable; import javax.inject.Named; // importation différente import javax.enterprise.context.SessionScoped; // importation différente @Named( ) @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; } }
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.
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)
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.
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.
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.
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.
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.
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.
@ManagedBean ou @Named public class GestionLivre { ... @PostConstruct public void début() { ... } ... @PreDestroy public void fin() { ... } }
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.
#{expression}
Les expressions EL peuvent utiliser la plupart des opérateurs Java habituels :
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]}
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= action=#{unBean.naviguer(1)} /> <h:commandButton value= 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) { ... } }
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. |
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 |
<?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= xmlns:h= xmlns:c=> <h:body style=background-color: yellow; font-weight: bold> <h3> <h:outputText value= /> <h:outputText value= rendered=#{view.locale == } /> </h3> <hr /> <c:forEach items=#{headerValues} var=> <h:outputText value=#{élément.key} style=color: mediumslateblue /> = <c:forEach var= items=#{élément.value}> <h:outputText value=#{valeur} escape= style=color: brown /> <br /> </c:forEach> </c:forEach> </h:body> </html>
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.
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 />
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 ; } }
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 ; } }
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 ; break; case 2 : return ; break; case 3 : return ; break; default : return null; break; } }
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.
?faces-redirect=true
<h:commandButton value=Accueil action=accueil?faces-redirect=true />
<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.
<h:button value=Accueil includeViewParams=true outcome=accueil> <f:param name=utilisateur value=#{bean.personne} > <f:param name=administrateur value=non > </h:button>
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.
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 :
<?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>
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.
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 ; } public void calcul() { if (tentative<tentativeMax) { tentative++; test = alea == valeur; } } public String getRésultat() { if (test) return ; if (tentative==tentativeMax) return ; if (tentative==0) return ; else return ; } public String getProgression() { if (tentative==0 || test) return ; return +(alea>valeur ? : ); } public int getAlea() { return alea; } public boolean isFin() { return test || tentative == tentativeMax; } }
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é.
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
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; }
Dans le projet, nous disposons de deux vues :
<?xml version="1.0" encoding="UTF-8"?> <html xmlns= xmlns:h=> <h:head> <title>Recherche d'un nombre aléatoire</title> </h:head> <h:outputStylesheet name= /> <h:body> <h:form> <h:panelGrid columns=> <h:outputText value= styleClass=normal /> <h:inputText value=#{nombre.valeurMax} styleClass=saisie/> <h:outputText value= styleClass=normal/> <h:inputText value=#{nombre.tentativeMax} styleClass=saisie/> </h:panelGrid> <hr /> <h:commandButton value= action=#{nombre.recommencer} /> </h:form> </h:body> </html>
public String recommencer() { alea = (int)(Math.random()*valeurMax)+1; tentative = 0; valeur = 0; test = false; return ; }
<?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= xmlns:h= xmlns:f=> <h:head> <title>Recherche d'un nombre aléatoire</title> </h:head> <h:outputStylesheet name= /> <h:body> <h:form> <h:panelGrid columns=> <h:outputText value= /> <h:inputText value=#{nombre.valeur} styleClass=saisie /> <h:outputText value=#{nombre.progression} styleClass=progression /> <h:outputFormat value=> <f:param value=#{nombre.tentative} /> </h:outputFormat> <h:inputText value=#{nombre.tentative} styleClass=saisie disabled= /> </h:panelGrid> <hr /> <h:commandButton value= action=#{nombre.calcul} disabled=#{nombre.fin} /> <h:commandButton value= action=#{nombre.recommencer} /> <h:commandButton value= action= /> <hr /> <h2> <h:outputText value=#{nombre.résultat} /> <h:outputText value=#{nombre.alea} styleClass=saisie rendered=#{nombre.fin} /> </h2> </h:form> </h:body> </html>
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. |
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. |
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.
<h:outputText value=> <f:converter converterId= /> <f:param name= 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)
<h:outputFormat value=> <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.
<h:dataTable value=#{auteurs} var= headerClass=inverse columnClasses=ligne> <h:column> <f:facet name=>Nom</f:facet> #{élément.nom} </h:column> <h:column> <f:facet name=>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.
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 :
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 ( ). |
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. |
L'attribut polyvalent id permet de réaliser les choses suivantes :
<h:inputText value=#{conv.euro} id=euro converterMessage= validatorMessage= required= requiredMessage=> <f:convertNumber type= currencySymbol=/> <f:validateDoubleRange minimum= /> </h:inputText> <h:message for=euro styleClass=erreur/>
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.
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.
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é} ... />
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=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=rendered=#{bean.ongletSélectionné == 'vidéo'} > <h:outputText... /> <h:commandButton... /> ... </h:pannelGrid>
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=value=#{bean.propriété} /> // code proposé en JSF ... <input size=type= ... /> // 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. |
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=value=#{bean.propriété} /> <h:inputText style=value=#{bean.propriété} />
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.
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" />
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. |
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>.
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= xmlns:h=> <h:body style=background-color: orange> <h:form> <h:outputText value= /> <hr /> <h:panelGrid columns= border=> <h:outputText value= /> <h:inputText value= /> <h:outputText value= /> <h:inputText value= /> <h:outputText value= /> <h:inputSecret value= /> </h:panelGrid> <hr /> <h:commandButton value= /> </h:form> </h:body> </html> |
|
<html xmlns= xmlns:h=> <h:body style=background-color: orange> <h:form> <h:outputText value= /> <hr /> <h:panelGrid columns=> <h:outputText value= /> <h:inputText value= /> <h:outputText value= /> <h:inputText value= /> <h:outputText value= /> <h:inputSecret value= /> </h:panelGrid> <hr /> <h:commandButton value= /> </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= xmlns:h= xmlns:f=> <style type=> body {background-color: green; color: yellow; } .ligne {background-color: #005500; } .saisie {background-color: greenyellow; } </style> <h:body> <h:form> <h:panelGrid columns= styleClass=ligne> <f:facet name=> <h:outputText value= /> </f:facet> <h:outputText value= /> <h:inputText value= styleClass=saisie/> <h:outputText value= /> <h:inputText value= styleClass=saisie /> <h:outputText value= /> <h:panelGroup> <h:inputSecret value= styleClass=saisie /> <h:outputText value= /> </h:panelGroup> <f:facet name=> <h:commandButton value= /> </f:facet> </h:panelGrid> </h:form> </h:body> </html> |
|
<html xmlns= xmlns:h= xmlns:f=> <style type=> 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= rowClasses= footerClass= headerClass=> <f:facet name=> <h:outputText value= /> </f:facet> <h:outputText value= /> <h:inputText value= styleClass=saisie/> <h:outputText value= /> <h:inputText value= styleClass=saisie /> <h:outputText value= /> <h:panelGroup> <h:inputSecret value= styleClass=saisie size=/> <h:outputText value= /> </h:panelGroup> <f:facet name=> <h:commandButton value= /> </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 :
|
<html xmlns= xmlns:h= xmlns:f=> <style type=> 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= rowClasses= footerClass= headerClass=> <f:facet name=> <h:outputText value= /> </f:facet> <h:outputText value= /> <h:inputText value= styleClass=saisie/> <h:outputText value= /> <h:inputText value= styleClass=saisie /> <h:outputText value= /> <h:panelGroup> <h:inputSecret value= styleClass=saisie size=/> <h:outputText value= /> </h:panelGroup> <f:facet name=> <h:commandButton value= /> </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 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= /> <h:inputSecret value=maxlength= /> <h:inputText value= /> <h:inputText value=> <h:inputTextarea value= />
<html xmlns= xmlns:h=> <h:body style=background-color: green; color: yellow; font-weight: bold> <h:form> <h:panelGrid columns=> <h:outputText value= /> <h:inputText value=#{test.valeur} readonly=/> <h:outputText value= /> <h:inputSecret value=#{test.motPasse} redisplay= /> <h:outputText value= /> <h:inputSecret value=#{test.motPasse} redisplay= /> <h:outputText value= /> <h:inputText value=#{test.valeur} style=background: yellow; color: green/> <h:outputText value= /> <h:inputText value=#{test.valeur} size=/> <h:outputText value= /> <h:inputText value=#{test.valeur} size= maxlength= /> </h:panelGrid> <hr /> <h:commandButton value= /> </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 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é.
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 : , par exemple. Vous devez toute de même utiliser cette balise dans les circonstances suivantes :
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 < > et & afin de conserver l'affichage original. Vous devez positionner cet attribut à false si vous désirez générer des balises HTML par programme.
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>
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>
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 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é. |
<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= />
Attributs | Description |
---|---|
action | (<h:commandButton> et <h:commandLink> uniquement) |
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 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= /> <f:selectItem itemValue= /> <f:selectItem itemValue= /> <f:selectItem itemValue= /> <f:selectItem itemValue= /> <f:selectItem itemValue= /> <f:selectItem itemValue= /> </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= 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= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectManyCheckbox> <hr /> <h:commandButton value= /> <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= border= cellspacing=> <h:panelGroup> <h:selectManyCheckbox value=#{test.couleurs} layout= > <f:selectItem itemLabel= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectManyCheckbox> </h:panelGroup> <h:panelGroup> <h:commandButton value= /> <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= border= cellspacing=> <h:panelGroup> <h:selectOneRadio value=#{test.couleur} layout= > <f:selectItem itemLabel= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneRadio> </h:panelGroup> <h:panelGroup> <h:commandButton value= /> <hr /> Choix : <br /> <h:inputTextarea value=#{test.couleur} readonly= style=background: #{test.couleur}/> </h:panelGroup> </h:panelGrid> |
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= border= cellspacing=> <h:panelGroup> <h:selectOneListbox value=#{test.couleur} > <f:selectItem itemLabel= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneListbox> </h:panelGroup> <h:panelGroup> <h:commandButton value= /> <hr /> <h:inputText value=#{test.couleur} readonly= style=background: #{test.couleur} size= /> </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= border= cellspacing=> <h:panelGroup> <h:selectManyListbox value=#{test.couleurs} layout= > <f:selectItem itemLabel= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectManyListbox> </h:panelGroup> <h:panelGroup> <h:commandButton value= /> <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= border= cellspacing=> <h:panelGroup> <h:selectOneMenu value=#{test.couleur} > <f:selectItem itemLabel= itemValue=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneMenu> </h:panelGroup> <h:panelGroup> <h:commandButton value= /> <hr /> <h:inputText value=#{test.couleur} readonly= style=background: #{test.couleur} size= /> </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. |
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= style=background: darkgreen cellpadding=> <h:panelGroup> <h:selectOneMenu value=#{test.couleur} id=choix required= requiredMessage=> <f:selectItem itemLabel= noSelectionOption= /> <f:selectItem itemLabel= itemValue= itemDisabled=/> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneMenu> <h:commandButton value= /> </h:panelGroup> <h:message for= /> <h:inputText value=#{test.couleur} readonly= style=background: #{test.couleur} size= /> </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= /> 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: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.
<h:body style=background-color: green; color: yellow; font-weight: bold> <h:form> <h:panelGrid columns= style=background: darkgreen cellpadding=> <h:panelGroup> <h:selectOneMenu value=#{test.couleur} id=choix required= requiredMessage=> <f:selectItems value=#{test.couleurs} /> </h:selectOneMenu> <h:commandButton value= /> </h:panelGroup> <h:message for= /> <h:inputText value=#{test.couleur} readonly= style=background: #{test.couleur} size= /> </h:panelGrid> </h:form> </h:body>
import javax.faces.bean.*; import javax.faces.model.SelectItem; @ManagedBean public class Test { private String couleur; private SelectItem[] couleurs = { new SelectItem(null, , , false, false, true), new SelectItem( , , , true), new SelectItem( , ), new SelectItem( , ), new SelectItem( , ), new SelectItem( , ) }; 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=> 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= style=background: darkgreen cellpadding=> <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= size= 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; } } |
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 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>.
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=> 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= styleClass=cadre> <f:facet name=> <h:messages styleClass=erreur/> </f:facet> Nom : <h:inputText id=nom value=#{test.nom} required= label=/> <h:message for= errorClass=erreur /> Âge : <h:inputText id=âge value=#{test.âge} size= label= /> <h:message for= errorClass=erreur /> <f:facet name=> <hr /> <h:commandButton value= /> </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=> 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= styleClass=cadre> Nom : <h:inputText id=nom value=#{test.nom} required= requiredMessage= /> <h:message for= errorClass=erreur /> Âge : <h:inputText id=âge value=#{test.âge} size= converterMessage= /> <h:message for= errorClass=erreur /> <f:facet name=> <hr /> <h:commandButton value= /> </f:facet> </h:panelGrid> </h:form> </h:body>
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= 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.
<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= styleClass=cadre> <h:outputText value= /> <h:inputText value=#{gestion.personne.prénom} styleClass=saisie/> <h:outputText value= /> <h:inputText value=#{gestion.personne.nom} styleClass=saisie /> <f:facet name=> <hr /> <h:commandButton value= action=#{gestion.nouveau()}/> <h:commandButton value= action=#{gestion.ajout()} /> </f:facet> </h:panelGrid> <br /> <h:dataTable value=#{gestion.personnes} var= 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. |
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= styleClass=cadre > <h:outputText value= /> <h:inputText value=#{gestion.personne.prénom} styleClass=saisie/> <h:outputText value= /> <h:inputText value=#{gestion.personne.nom} styleClass=saisie /> <f:facet name=> <hr /> <h:commandButton value= action=#{gestion.nouveau()}/> <h:commandButton value= 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= > <f:facet name=>Liste des personnes</f:facet> <h:column> <f:facet name=>Prénom</f:facet> #{personne.prénom} </h:column> <h:column> <f:facet name=>Nom</f:facet> #{personne.nom} </h:column> </h:dataTable> </h:form> </h:body> |
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= xmlns:h= xmlns:f=> <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=>Marié</f:facet> <h:selectBooleanCheckbox value=#{personne.marié} /> </h:column> <h:column headerClass=> <f:facet name=>Prénom</f:facet> <h:inputText value=#{personne.prénom} /> </h:column> <h:column headerClass=> <f:facet name=>Nom</f:facet> <h:inputText value=#{personne.nom} /> </h:column> <h:column><h:commandButton value= /></h:column> <h:column> <h:commandButton value= action=#{gestion.supprimer(personne)} /> </h:column> <f:facet name=> <hr /> <h:commandButton value= 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é.
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 :
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.
package entité; import java.io.Serializable; import javax.persistence.*; @Entity @NamedQuery(name= , query= ) 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() { } }
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( ); 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; } }
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; }
<?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= xmlns:h= xmlns:f=> <h:head /> <h:outputStylesheet name= library= /> <h:body> <h:form> <h:dataTable value=#{gérer.conversions} var=conversion styleClass=cadre footerClass=fond headerClass=fond rows= first=#{gérer.page * 4}> <f:facet name=> <h:inputText value=#{gérer.euro} styleClass=saisie> <f:convertNumber type= currencySymbol=/> </h:inputText> <h:selectOneMenu value=#{gérer.sens}> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneMenu> <h:inputText value=#{gérer.franc} styleClass=saisie> <f:convertNumber type= currencySymbol=/> </h:inputText> <h:commandButton value= action=#{gérer.convertir()} /> </f:facet> <h:column headerClass=> <h:outputText value=#{conversion.euro} styleClass=resultat> <f:convertNumber type= currencySymbol= /> </h:outputText> </h:column> <h:column headerClass=> <h:selectOneRadio value=#{conversion.sens}> <f:selectItem itemLabel= itemValue= /> <f:selectItem itemLabel= itemValue= /> </h:selectOneRadio> </h:column> <h:column headerClass=> <h:outputText value=#{conversion.franc} styleClass=resultat> <f:convertNumber type= currencySymbol= /> </h:outputText> </h:column> <h:column> </h:column> <h:column> <h:commandButton value= action=#{gérer.supprimer(conversion)} /> </h:column> <f:facet name=> <h:outputFormat value= styleClass=pages> <f:param value=#{gérer.pages} /> </h:outputFormat> <h:commandButton value= disabled=#{gérer.page == 0} action=#{gérer.changerPage(-1)}/> <h:commandButton value= disabled=#{gérer.page == gérer.pages-1} action=#{gérer.changerPage(1)} /> <h:outputFormat value= styleClass=pages> <f:param value=#{gérer.pages} /> </h:outputFormat> </f:facet> </h:dataTable> </h:form> </h:body> </html>