Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | Supérieur |
Les technologies permettant de développer des applications web avec Java ne cessent d'évoluer :
Java Server Faces (JSF) est une technologie dont le but est de proposer un framework qui facilite et standardise le développement d'applications web avec Java. Son développement a tenu compte des différentes expériences acquises lors de l'utilisation des technologies standard pour le développement d'applications web (servlet, JSP, JSTL) et de différents frameworks (Struts, ...).
Le grand intérêt de JSF est de proposer un framework qui puisse être mis en oeuvre par des outils pour permettre un développement de type RAD pour les applications web et ainsi faciliter le développement des applications de ce type. Ce type de développement était déjà courant pour des applications standalone ou clients/serveurs lourds avec des outils tels que Delphi de Borland, Visual Basic de Microsoft ou Swing avec Java.
Ce concept n'est pourtant pas nouveau dans les applications web puisqu'il est déjà mis en oeuvre par WebObject d'Apple et plus récemment par ASP.Net de Microsoft mais cette mise en oeuvre à grande échelle fût relativement tardive. L'adoption du RAD pour le développement web trouve notamment sa justification dans le coût élevé de développement de l'IHM à la « main » et souvent par copier/coller d'un mélange de plusieurs technologies (HTML, JavaScript, ...), rendant fastidieux et peu fiable le développement de ces applications.
Plusieurs outils commerciaux intègrent déjà l'utilisation de JSF notamment Studio Creator de Sun, WSAD d'IBM, JBuilder de Borland, JDevelopper d'Oracle, ...
Même si JSF peut être utilisé par codage à la main, l'utilisation d'un outil est fortement recommandée pour pouvoir mettre en oeuvre rapidement toute la puissance de JSF.
Ainsi de par sa complexité et sa puissance, JSF s'adapte parfaitement au développement d'applications web complexes en facilitant leur écriture.
Les pages officielles de cette technologie sont à l'URL :
https://www.oracle.com/java/technologies/javaserverfaces.html.
La version 1.0 de Java Server Faces, développée sous la JSR-127, a été validée en mars 2004.
JSF est une technologie utilisée côté serveur dont le but est de faciliter le développement de l'interface utilisateur en séparant clairement la partie « interface » de la partie « métier » d'autant que la partie interface n'est souvent pas la plus compliquée mais la plus fastidieuse à réaliser.
Cette séparation avait déjà été initiée avec la technologie JSP et particulièrement les bibliothèques de tags personnalisés. Mais JSF va encore plus loin en reposant sur le modèle MVC et en proposant de mettre en oeuvre :
JSF se compose :
Le rendu des composants ne se limite pas à une seule technologie même si l'implémentation de référence ne propose qu'un rendu des composants en HTML.
Le traitement d'une requête gérée par une application utilisant JSF suit un cycle de vie particulier constitué de plusieurs étapes :
Ces différentes étapes sont transparentes lors d'une utilisation standard de JSF.
Ce chapitre contient plusieurs sections :
JSF utilise la notion de vue (view) qui est composée d'une arborescence ordonnée de composants inclus dans la page.
Les requêtes sont prises en charge et gérées par le contrôleur d'une application JSF (en général une servlet). Celle-ci va assurer la mise en oeuvre d'un cycle de vie des traitements en vue d'envoyer une réponse au client.
JSF propose pour chaque page un cycle de vie pour traiter la requête HTTP et générer la réponse. Ce cycle de vie est composé de plusieurs étapes :
Java Server Faces est une spécification : il est donc nécessaire d'obtenir une implémentation de la part d'un tiers.
Plusieurs implémentations commerciales ou libres sont disponibles, notamment l'implémentation de référence de Sun et MyFaces qui est devenu un projet du groupe Apache.
Comme pour toute JSR validée, Sun propose une implémentation de référence des spécifications de la JSR, qui est la plus complète possible.
Plusieurs versions de l'implémentation de référence de Sun sont proposées :
Version | Date de diffusion |
1.0 | Mars 2004 |
1.1 | Mai 2004 |
1.1_01 | Septembre 2004 |
La solution la plus simple pour utiliser l'implémentation de référence est d'installer le JWSDK 1.3 qui est fourni en standard avec l'implémentation de référence de JSF. La version de JSF fournie avec le JWSDK 1.3 est la 1.0.
Pour utiliser la version 1.1, il faut supprimer le répertoire jsf dans le répertoire d'installation de JWSDK, télécharger l'implémentation de référence, décompresser son contenu dans le répertoire d'installation de JWSDK et renommer le répertoire jsf-1_1_01 en jsf.
Il est aussi possible de télécharger l'implémentation de référence sur le site de Sun et de l'installer « manuellement » dans un conteneur web tel que Tomcat. Cette procédure sera détaillée dans une des sections suivantes.
Pour cela, il faut télécharger le fichier jsf-1_1_01.zip et le décompresser dans un répertoire du système. L'archive contient les bibliothèques de l'implémentation, la documentation des API et des exemples.
Les exemples de ce chapitre vont utiliser cette version 1.1 de l'implémentation de référence des JSF.
MyFaces est une implémentation libre des Java Server Faces qui est devenue un projet du groupe Apache. Elle propose plusieurs composants spécifiques en plus de ceux imposés par les spécifications JSF. |
Le site de MyFaces est à l'URL : https://myfaces.apache.org/
Il faut télécharger le fichier et le décompresser dans un répertoire du système. Il suffit alors de copier le fichier myfaces-examples.war dans le répertoire webapps de Tomcat, de relancer le serveur puis saisir l'URL http://localhost:8080/myfaces-examples
Pour utiliser MyFaces dans ses propres applications, il faut réaliser plusieurs opérations.
Il faut copier les fichiers *.jar du répertoire lib de MyFaces et myfaces-jsf-api.jar dans le répertoire WEB-INF/lib de la webapp.
Dans chaque page qui va utiliser les composants de MyFaces, il faut déclarer la bibliothèque de tags dédiés.
Exemple : |
<%@ taglib uri="http://myfaces.sourceforge.net/tld/myfaces_ext_0_9.tld" prefix="x"%>
Les applications utilisant JSF sont des applications web qui doivent respecter les spécifications de J2EE.
En tant que telles, elles doivent avoir la structure définie par J2EE pour toutes les applications web :
/
/WEB-INF
/WEB-INF/web.xml
/WEB-INF/lib
/WEB-INF/classes
Le fichier web.xml doit contenir au minimum certaines informations notamment, la servlet faisant office de contrôleur, le mapping des URL pour cette servlet et des paramètres.
Exemple : |
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Test JSF</display-name>
<description>Application de tests avec JSF</description>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<!-- Faces Servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup> 1 </load-on-startup>
</servlet>
<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
</web-app>
Chaque implémentation nécessite un certain nombre de bibliothèques tierces pour son bon fonctionnement.
Par exemple, pour l'implémentation de référence, les bibliothèques suivantes sont nécessaires :
jsf-api.jar
jsf-ri.jar
jstl.jar
standard.jar
common-beanutils.jar
commons-digester.jar
commons-collections.jar
commons-logging.jar
Remarque : avec l'implémentation de référence, il n'y a aucun fichier .tld à copier car ils sont intégrés dans le fichier jsf-impl.jar.
Les fichiers nécessaires dépendent de l'implémentation utilisée.
Ces bibliothèques peuvent être mises à disposition de l'application selon plusieurs modes :
L'avantage de la première solution est de faciliter la portabilité de l'application sur différents conteneur web mais elle duplique ces fichiers si plusieurs applications utilisent JSF.
Les avantages et inconvénients de la première solution sont exactement à l'opposé de ceux de la seconde solution. Le choix de l'une ou l'autre est donc à faire en fonction du contexte de déploiement.
Toute application utilisant JSF doit posséder au moins deux fichiers de configuration qui vont contenir les informations nécessaires à sa bonne exécution.
Le premier fichier est le descripteur de toute application web J2EE : le fichier web.xml contenu dans le répertoire WEB-INF.
Le second fichier est un fichier de configuration au format XML, particulier au paramétrage de JSF et nommé faces-config.xml.
Le fichier web.xml doit contenir au minimum certaines informations notamment, la servlet servant de contrôleur, le mapping des URLs pour cette servlet et des paramètres pour configurer JSF.
Exemple : |
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Test JSF</display-name>
<description>Application de tests avec JSF</description>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<!-- Servlet servant de controleur-->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup> 1 </load-on-startup>
</servlet>
<!-Le mapping de la servlet -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
</web-app>
Le tag <servlet> permet de définir une servlet et plus particulièrement dans ce cas de préciser la servlet qui sera utilisée comme contrôleur dans l'application. Le plus simple est d'utiliser la servlet fournie avec l'implémentation de référence javax.faces.webapp.FacesServlet. Le tag <load-on-startup> avec comme valeur 1 permet de demander le chargement de cette servlet au lancement de l'application.
Le tag <servlet-mapping> permet de préciser le mapping des URLs qui seront traitées par la servlet. Ce mapping peut prendre deux formes :
Les URL utilisées pour des pages mettant en oeuvre JSF doivent obligatoirement passer par cette servlet. Ces URLs peuvent être de deux formes selon le mapping défini.
Exemple :
Dans les deux cas, c'est la servlet utilisée comme contrôleur qui va déterminer le nom de la page JSP à utiliser.
Le paramètre de contexte javax.faces.STATE_SAVING_METHOD permet de préciser le mode d'échange de l'état de l'arbre des composants de la page. Deux valeurs sont possibles :
Il est possible d'utiliser l'extension .jsf pour les fichiers JSP utilisant JSF à condition de correctement configurer le fichier web.xml dans ce sens. Pour cela deux choses sont à faire :
<servlet-mapping>
<servlet-name>jsp</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.jsf</param-value>
</context-param>
Le démarrage d'une application directement avec une page par défaut utilisant JSF ne fonctionne pas correctement. Il est préférable d'utiliser une page HTML qui va effectuer une redirection vers la page d'accueil de l'application
Exemple : |
<html>
<head>
<meta http-equiv="Refresh" content= "0; URL=index.faces"/>
<title>Demarrage de l'application</title>
</head>
<body>
<p>Démarrage de l'application ...</p>
</body>
</html>
Il suffit alors de préciser dans le fichier web.xml que cette page est la page par défaut de l'application.
Exemple : |
...
<welcome-file-list>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
...
Le plus simple est de placer ce fichier dans le répertoire WEB-INF de l'application Web.
Il est aussi possible de préciser son emplacement dans un paramètre de contexte nommé javax.faces.application.CONFIG_FILES dans le fichier web.xml. Il est possible par ce biais de découper le fichier de configuration en plusieurs morceaux. Ceci est particulièrement intéressant pour de grosses applications car un seul fichier de configuration peut dans ce cas devenir très gros. Il suffit de préciser chacun des fichiers séparés par une virgule dans le tag <param-value>.
Exemple : |
...
<context-param>
<param-name>javax.faces.application.CONFIG_FILES</param-name>
<param-value>
/WEB-INF/ma-faces-config.xml, /WEB-INF/navigation-faces.xml, /WEB-INF/beans-faces.xml
</param-value>
</context-param>
...
Ce fichier au format XML permet de définir et de fournir des valeurs d'initialisation pour des ressources nécessaires à l'application utilisant JSF.
Ce fichier doit impérativement respecter la DTD proposée par les spécifications de JSF :
http://java.sun.com/dtd/web-facesconfig_1_0.dtd
Le tag racine du document XML est le tag <face-config>. Ce tag peut avoir plusieurs tags fils :
Tag | Rôle |
application | permet de préciser ou de remplacer des éléments de l'application |
factory | permet de remplacer des fabriques par des fabriques personnalisées de certaines ressources (FacesContextFactory, LifeCycleFactory, RenderKitFactory, ...) |
component | définit un composant graphique personnalisé |
converter | définit un convertisseur pour encoder/décoder les valeurs des composants graphiques (conversion de String en Object et vice versa) |
managed-bean | définit un objet utilisé par un composant qui est automatiquement créé, initialisé et stocké dans une portée précisée |
navigation-rule | définit les règles qui permettent de déterminer l'enchaînement des traitements de l'application |
referenced-bean | |
render-kit | définit un kit pour le rendu des composants graphiques |
lifecycle | |
validator | définit un validateur personnalisé de données saisies dans un composant graphique |
Ces tags fils peuvent être utilisés 0 ou plusieurs fois dans le tag <face-config>.
Le tag <application> permet de préciser des informations sur les entités utilisées par l'internationalisation et/ou de remplacer des éléments de l'application.
Les éléments à remplacer peuvent être : ActionListener, NavigationHandler, ViewHandler, PropertyResolver, VariableResolver. Ceci n'est utile que si la version fournie dans l'implémentation ne correspond pas aux besoins et doit être personnalisée par l'écriture d'une classe dédiée.
Le tag fils <message-bundle> permet de préciser le nom de base des fichiers de ressources utiles à l'internationalisation.
Le tag <locale-config> permet de préciser les locales qui sont supportées par l'application. Il faut utiliser autant de tags fils <supported-locale> que de locales supportées. Le tag fil <default-locale> permet de préciser la locale par défaut.
Exemple : |
...
<application>
<message-bundle>fr.jmdoudoux.dej.jsf.monapp.bundles.Messages</message-bundle>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
</application>
...
Les beans sont largement utilisés dans une application JSF notamment pour permettre l'échange de données entre les différentes entités et le traitement des événements.
Les beans sont des classes qui respectent une spécification particulière notamment la présence :
Les beans managés sont des javabeans dont le cycle de vie va être contrôlé par le framework JSF en fonction des besoins et du paramétrage fourni dans le fichier de configuration.
Dans le fichier de configuration, chacun de ces beans doit être déclaré avec un tag <managed-bean>. Ce tag possède trois tags fils obligatoires :
La portée peut prendre les valeurs suivantes :
Exemple : |
...
<managed-bean>
<managed-bean-name>login</managed-bean-name>
<managed-bean-class>fr.jmdoudoux.dej.jsf.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
...
Il est possible de fournir des valeurs par défaut aux propriétés en utilisant le tag <managed-property>. Ce tag possède deux tags fils :
Exemple : |
...
<managed-bean>
<managed-bean-name>login</managed-bean-name>
<managed-bean-class>fr.jmdoudoux.dej.jsf.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>nom</property-name>
<value>test</value>
</managed-property>
</managed-bean>
...
Lorsque le bean sera instancié, JSF appellera automatiquement les setters des propriétés identifiées dans des tags <managed-property> avec leurs valeurs <value> respectives.
Pour initialiser la propriété à null, il faut utiliser le tag <null-value>
Exemple : |
...
<managed-property>
<property-name>nom</property-name>
<null-value>
</managed-property>
...
Ces informations seront utilisées par JSF pour automatiser la création ou la récupération d'un bean lorsque celui-ci sera utilisé dans l'application.
Le grand intérêt de ce mécanisme est de ne pas avoir à se soucier de l'instanciation du bean ou de sa recherche dans la portée puisque c'est le framework qui va s'en occuper de façon transparente.
Il est toujours nécessaire dans la partie présentation d'obtenir la valeur d'une donnée d'un bean pour par exemple l'afficher.
JSF propose une syntaxe basée sur des expressions qui facilitent l'utilisation des valeurs d'un bean. Ces expressions doivent être délimitées par #{ et }.
Basiquement une expression est composée du nom du bean suivi du nom de la propriété désirée séparés par un point.
Exemple : |
<h:inputText value="#{login.nom}"/>
Cet exemple affecte la valeur de l'attribut nom du bean login au composant de type saisie de texte. Dans ce cas précis, c'est aussi cet attribut de ce bean qui recevra la valeur saisie lorsque la page sera envoyée au serveur.
En fonction du contexte le résultat de l'évaluation peut conduire à l'utilisation du getter (par exemple pour afficher la valeur) ou du setter (pour affecter la valeur après un envoi de la page). C'est JSF qui le détermine en fonction du contexte.
La notation par point peut être remplacée par l'utilisation de crochets. Dans ce cas, le nom de la propriété doit être mis entre simples ou doubles quotes dans les crochets.
Exemple : |
login.nom
login["nom"]
login['nom']
Ces trois expressions sont rigoureusement identiques. Cette syntaxe peut être plus pratique lors de la manipulation de collections mais elle est obligatoire lorsque la propriété contient un point.
Exemple : |
msg["login.titre"]
L'utilisation des quotes simples ou doubles est équivalente car il faut les imbriquer par exemple lors de leur utilisation comme valeur de l'attribut d'un composant.
Exemple : |
<h:inputText value='#{login["nom"]}'/>
<h:inputText value="#{login['nom]'}"/>
Attention, la syntaxe utilisée par JSF est proche mais différente de celle proposée par JSTL : JSF utilise le délimiteur #{ ... } et JSTL utilise le délimiteur ${ ... } .
JSF définit un ensemble de variables prédéfinies et utilisables dans les expressions de liaison de données :
Variable | Rôle |
header | une collection de type Map encapsulant les éléments définis dans les paramètres de l'en-tête de la requête http (seule la première valeur est renvoyée) |
header-values | une collection de type Map encapsulant les éléments définis dans les paramètres de l'en-tête de la requête http (toutes les valeurs sont renvoyées sous la forme d'un tableau) |
param | une collection de type Map encapsulant les éléments définis dans les paramètres de la requête http (seule la première valeur est renvoyée) |
param-values | une collection de type Map encapsulant les éléments définis dans les paramètres de la requête http (toutes les valeurs sont renvoyées sous la forme d'un tableau) |
cookies | une collection de type Map encapsulant les éléments définis dans les cookies |
initParam | une collection de type Map encapsulant les éléments définis dans les paramètres d'initialisation de l'application |
requestScope | une collection de type Map encapsulant les éléments définis dans la portée request |
sessionScope | une collection de type Map encapsulant les éléments définis dans la portée session |
applicationScope | une collection de type Map encapsulant les éléments définis dans la portée application |
facesContext | une instance de la classe FacesContext |
View | une instance de la classe UIViewRoot qui encapsule la vue |
Lorsque qu'une variable est utilisée dans une expression, JSF recherche dans la liste des variables prédéfinies, puis recherche une instance dans la portée request, puis dans la portée session et enfin dans la portée application. Si aucune instance n'est trouvée, alors JSF crée une nouvelle instance en tenant compte des informations du fichier de configuration. Cette instanciation est réalisée par un objet de type VariableResolver de l'application.
La syntaxe des expressions possède aussi quelques opérateurs :
Opérateurs | Rôle | Exemple |
+ - * / % div mod | opérateurs arithmétiques | |
< <= > >= == != lt le gt ge eq ne |
opérateurs de comparaisons | |
&& || ! and or not |
opérateurs logiques | <h:inputText rendered="#{!monBean.affichable}" /> |
Empty | opérateur vide : un objet null, une chaîne vide, un tableau ou une collection sans élément | |
? : | opérateur ternaire de test |
Il est possible de concaténer les résultats des évaluations de plusieurs expressions simplement en les plaçant les uns à la suite des autres.
Exemple : |
<h:outputText value="#{messages.salutation}, #{utilisateur.nom}!"/>
Il est parfois nécessaire d'évaluer une expression dans le code des objets métiers pour obtenir sa valeur. Comme tous les composants sont stockés dans le FaceContext, il est possible d'accéder à cet objet pour obtenir les informations désirées. Il est d'abord nécessaire d'obtenir l'instance courante de l'objet FaceContext en utilisant la méthode statique getCurrentInstance().
Exemple : |
FacesContext context = FacesContext.getCurrentInstance();
ValueBinding binding = context.getApplication().createValueBinding("#{login.nom}");
String nom = (String) binding.getValue(context);
Les beans de type backing bean sont spécialement utilisés avec JSF pour encapsuler tout ou partie des composants d'une page et ainsi faciliter leur accès notamment lors des traitements.
Ces beans sont particulièrement utiles durant des traitements réalisés lors de validations ou de gestion d'événements car ils permettent un accès aux composants dont ils possèdent une référence.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.component.UIInput;
public class LoginBean {
private UIInput composantNom;
private String nom;
private String mdp;
public UIInput getComposantNom() {
return composantNom;
}
public void setComposantNom(UIInput input) {
composantNom = input;
}
public String getNom() {
return nom;
}
...
}
Dans la vue, il est nécessaire de lier un composant avec son attribut correspondant dans le backing bean. L'attribut binding d'un composant permet de réaliser cette liaison.
Exemple : |
<h:inputText value="#{login.nom}" binding="#{login.composantNom}" />
JSF propose un ensemble de composants serveurs pour faciliter le développement d'interfaces graphiques utilisateur.
Pour les composants, JSF propose :
Tous ces composants héritent de la classe abstraite UIComponentBase.
JSF propose 12 composants de base :
UICommand | Composant qui permet de réaliser une action émettant un événement |
UIForm | Composant qui regroupe d'autres composants dont l'état sera renvoyé au serveur |
UIGraphic | Composant qui représente une image |
UIInput | Composant qui permet de saisir des données |
UIOutput | Composant qui permet d'afficher des données |
UIPanel | Composant qui regroupe d'autres composants à afficher sous la forme d'un tableau |
UIParameter | |
UISelectItem | Composant qui représente un élément sélectionné dans un ensemble |
UISelectItems | Composant qui représente un ensemble d'éléments |
UISelectBoolean | Composant qui permet de sélectionner parmi deux états |
UISelectMany | Composant qui permet de sélectionner plusieurs éléments d'un ensemble |
UISelectOne | Composant qui permet de sélectionner un seul élément d'un ensemble |
Ces classes sont des javabeans qui définissent les fonctionnalités de base des composants permettant la saisie et la sélection de données.
Chacun de ces composants possède un type, un identifiant, une ou plusieurs valeurs locales et des attributs. Ils sont extensibles et il est même possible de créer ses propres composants.
Le comportement de ces composants repose sur le traitement d'événements respectant le modèle de gestion des événements de JSF.
Ces classes ne sont pas utilisées directement : elles sont utilisées par la bibliothèque de tags personnalisés qui se charge de les instancier et de leur associer le modèle de rendu adéquat.
Ces classes ne prennent pas en charge le rendu du composant. Par exemple, un objet de type UICommand peut être rendu en HTML sous la forme d'un lien hypertexte ou d'un bouton de formulaire.
Pour chaque composant, il est possible de définir un ou plusieurs modèles qui se chargent du rendu de ce composant dans un contexte client particulier (par exemple HTML).
L'association entre un composant et son modèle de rendu est réalisée dans un RenderKit : il précise pour chaque composant le ou les modèles de rendu à utiliser. Par exemple, un objet de type UISelectOne peut être rendu sous la forme d'un ensemble de boutons radio, d'une liste ou d'une liste déroulante. Chacun de ces rendus est défini par un objet de type Renderer.
L'implémentation de référence propose un seul modèle de rendu pour les composants qui génèrent de l'HTML.
Ce modèle favorise la séparation entre l'état, le comportement d'un composant et sa représentation finale.
Le modèle de rendu permet de définir la représentation visuelle des composants. Chaque composant peut être rendu de plusieurs façons avec plusieurs modèles de rendu. Par exemple, un composant de type UICommand peut être rendu sous la forme d'un bouton ou d'un lien hypertexte. Le rendu peut être HTML mais il est possible d'utiliser un autre système de rendu comme XML ou WML.
Le modèle de rendu met un oeuvre un ou plusieurs kits de rendus.
Pour une utilisation dans une JSP, l'implémentation de référence propose deux bibliothèques de tags personnalisés :
Pour utiliser ces deux bibliothèques, il est nécessaire d'utiliser une directive taglib pour chacune d'elles au début de la page JSP.
Exemple : |
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
Le préfixe est libre mais par convention ce sont ceux fournis dans l'exemple qui sont utilisés.
Le tag <view> est obligatoire dans toutes pages utilisant JSF. Cet élément va contenir l'état de l'arborescence des composants de la page si l'application est configurée pour stocker l'état sur le client.
Le tag <form> génère un tag HTML form qui définit un formulaire.
Exemple : |
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head>
<title>Application de tests avec JSF</title>
</head>
<body>
<h:form>
...
</h:form>
</body>
</f:view>
</html>
Cette bibliothèque est composée de 18 tags.
Tag | Rôle |
actionListener | ajouter un listener pour une action sur un composant |
attribute | ajouter un attribut à un composant |
convertDateTime | ajouter un convertisseur de type DateTime à un composant |
convertNumber | ajouter un convertisseur de type numérique à un composant |
facet | définit un élément particulier d'un composant |
loadBundle | charger un fichier contenant les chaînes de caractères d'une locale dans une collection de type Map |
param | ajouter un paramètre à un composant |
selectitem | définir l'élément sélectionné dans un composant permettant de faire un choix |
selectitems | définir les éléments sélectionnés dans un composant permettant de faire un choix |
subview | définir une sous-vue |
verbatim | ajouter un texte brut à la vue |
view | définir une vue |
validator | ajouter un validateur à un composant |
validateDoubleRange | ajouter un validateur de type « plage de valeurs réelles » à un composant |
validateLength | ajouter un validateur de type « taille de la valeur » à un composant |
validateLongRange | ajouter un validateur de type « plage de valeurs entières » à un composant |
valueChangeListener | ajouter un listener pour un changement de valeur sur un composant |
La plupart de ces tags permettent d'ajouter des objets à un composant. Leur utilisation sera détaillée tout au long de ce chapitre.
Ce tag représente un élément dans un composant qui peut en contenir plusieurs.
Les attributs de base sont les suivants :
Attribut | Rôle |
itemValue | contient la valeur de l'élément |
itemLabel | contient le libellé de l'élément |
itemDescription | contient une description de l'élément (utilisé uniquement par les outils de développement) |
itemDisabled | contient l'état de l'élément |
binding | contient le nom d'une méthode qui renvoie un objet de type javax.faces.model.SelectItem |
id | contient l'identifiant du composant |
value | contient une expression qui désigne un objet de type javax.faces.model.SelectItem |
Exemple : |
<f:selectItem value="#{test.elementSelectionne}"/>
L'attribut value attend en paramètre une expression désignant une méthode qui renvoie un objet de type SelectItem. Cet objet encapsule l'objet de la liste qui sera sélectionné.
Exemple : |
...
public SelectItem getElementSelectionne() {
return new SelectItem("Element 1");
}
...
La classe SelectItem possède quatre constructeurs qui permettent de définir les différentes propriétés qui composent l'élément.
Ce tag représente une collection d'éléments dans un composant qui peut en contenir plusieurs.
Ce tag est particulièrement utile car il évite d'utiliser autant de tags selectItem que d'éléments à définir.
Exemple : |
...
<h:selectOneRadio>
<f:selectItems value="#{test.listeElements}"/>
</h:selectOneRadio>
...
La collection d'objets de type SelectItem peut être soit une collection soit un tableau.
Exemple : avec un tableau d'objects de type SelectItem |
package fr.jmdoudoux.dej.jsf;
import javax.faces.model.SelectItem;
public class TestBean {
private SelectItem[] elements = {
new SelectItem(new Integer(1), "Element 1"),
new SelectItem(new Integer(2), "Element 2"),
new SelectItem(new Integer(3), "Element 3"),
new SelectItem(new Integer(4), "Element 4"),
};
public SelectItem[] getListeElements() {
return elements;
}
...
}
La collection peut être de type Map : dans ce cas le framework associe la clé de chaque occurrence à la propriété itemValue et la valeur à la propriété itemLabel
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.HashMap;
import java.util.Map;
import javax.faces.model.SelectItem;
public class TestBean {
private Map elements = null;
public Map getListeElements() {
if (elements == null) {
elements = new HashMap();
elements.put("Element 1", new Integer(1));
elements.put("Element 2", new Integer(2));
elements.put("Element 3", new Integer(3));
elements.put("Element 4", new Integer(4));
}
return elements;
}
public SelectItem getElementSelectionne() {
return new SelectItem("Element 1");
}
...
}
Ce tag permet d'insérer du texte dans la vue.
Son utilisation est obligatoire dans le corps des tags JSF pour insérer autre chose qu'un tag JSF. Par exemple, pour insérer un tag HTML dans le corps d'un tag JSF, il est obligatoire d'utiliser le tag <verbatim>.
Les tags suivants peuvent avoir un corps : commandLink, outputLink, panelGroup, panelGrid et dataTable.
Exemple : |
<h:outputLink value="http://java.sun.com" title="Java">
<f:verbatim>
Site Java de Sun
</f:verbatim>
</h:outputLink>
Il est possible d'utiliser le tag <outputText> à la place du tag <verbatim>.
Ce tag permet de fournir un attribut quelconque à un composant puisque chaque composant peut stocker des attributs arbitraires.
Ce tag possède deux attributs :
Attribut | Rôle |
Name | nom de l'attribut |
Value | valeur de l'attribut |
Dans le code d'un composant, il est possible d'utiliser la méthode getAttributes() pour obtenir une collection de type Map des attributs du composant.
Ceci offre un mécanisme souple pour fournir des paramètres sans être obligé de créer un nouveau composant ou de modifier un composant existant en lui ajoutant un ou plusieurs attributs.
Ce tag permet de définir des éléments particuliers d'un composant.
Il est par exemple utilisé pour définir les lignes d'en-tête et de pied de page des tableaux.
Ce tag possède plusieurs attributs :
Attribut | Rôle |
Name | Permet de préciser le type de l'élément généré par le tag Les valeurs possibles sont header et footer |
Exemple : |
<h:column>
<f:facet name="header">
<h:outputText value="Nom" />
</f:facet>
<h:outputText value="#{personne.nom}"/>
</h:column>
Cette bibliothèque est composée de 25 tags qui permettent la réalisation de l'interface graphique de l'application.
Tag | Rôle |
form | le tag <form> HTML |
commandButton | un bouton |
commandLink | un lien qui agit comme un bouton |
graphicImage | une image |
inputHidden | une valeur non affichée |
inputSecret | une zone de saisie de texte monoligne dont la valeur n'est pas lisible |
inputText | une zone de saisie de texte monoligne |
inputTextarea | une zone de saisie de texte multiligne |
outputLink | un lien |
outputFormat | du texte affiché avec des valeurs fournies en paramètre |
outputText | du texte affiché |
panelGrid | un tableau |
panelGroup | un panneau permettant de regrouper plusieurs composants |
selectBooleanCheckbox | une case à cocher |
selectManyCheckbox | un ensemble de cases à cocher |
selectManyListbox | une liste déroulante où plusieurs éléments sont sélectionnables |
selectManyMenu | un menu où plusieurs éléments sont sélectionnables |
selectOneListbox | une liste déroulante où un seul élément est sélectionnable |
selectOneMenu | un menu où un seul élément est sélectionnable |
selectOneRadio | un ensemble de boutons radio |
dataTable | une grille proposant des fonctionnalités avancées |
column | une colonne d'une grille |
message | le message d'erreur lié à un composant |
messages | les messages d'erreur liés à tous les composants |
Ces tags possèdent des attributs communs pouvant être regroupés en trois catégories :
Chaque tag utilise ou non chacun de ces attributs.
Les attributs de base sont les suivants :
Attribut | Rôle |
id | contient l'identifiant du composant |
binding | permet l'association avec un backing bean |
rendered | contient un booléen qui indique si le composant doit être affiché |
styleClass | contient le nom d'une classe CSS à appliquer au composant |
value | contient la valeur du composant |
valueChangeListener | permet l'association à une méthode qui va traiter les changements de valeurs |
converter | contient une classe de conversion des données de chaîne de caractères en objet et vice versa |
validator | contient une classe de validation des données |
required | contient un booléen qui indique si une valeur doit obligatoirement être saisie |
L'attribut id est très important car il permet d'avoir accès :
<h:message for="nom"/>
L'attribut binding permet d'associer le composant avec un champ d'une classe de type bean. Un tel bean est nommé backing bean dans une application JSF.
Exemple : |
...
<h:inputText value="#{login.nom}" id="nom" required="true" binding="#{login.inputTextNom}"/>
..
...
import javax.faces.component.UIComponent;
public class LoginBean {
private String nom;
private UIComponent inputTextNom;
public UIComponent getInputTextNom() {
return inputTextNom;
}
public void setInputTextNom(UIComponent inputTextNom) {
this.inputTextNom = inputTextNom;
}
...
L'attribut value permet de préciser la valeur d'un tag. Cette valeur peut être fournie sous deux formes :
L'attribut converter permet de préciser une classe qui va convertir la valeur d'un objet en chaîne de caractères et vice versa. L'utilisation de cet attribut est détaillée dans une des sections suivantes.
L'attribut validator permet de préciser une classe qui va réaliser des contrôles de validation sur la valeur saisie. L'utilisation de cet attribut est détaillée dans une des sections suivantes.
L'attribut styleClass permet de préciser le nom d'un style défini dans une feuille de style CSS qui sera appliqué au composant.
Exemple : le fichier monstyle.css |
.titre {
color:red;
}
Dans la vue, il faut inclure la feuille de style dans la partie en-tête de la page HTML.
Exemple : |
...
<link href="monstyle.css" rel="stylesheet" type="text/css"/>
...
<h:outputText value="#{msg.login_titre}" styleClass="titre"/>
...
L'attribut renderer permet de préciser si le composant sera affiché ou non dans la vue. La valeur de l'attribut peut être obtenue dynamiquement par l'utilisation du langage d'expression.
Exemple : |
<h:panelGrid rendered='#{listepersonnes.nbOccurrences gt 0}'/>
Les principaux attributs liés à HTML sont les suivants :
Attribut | Rôle |
accesskey | contient le raccourci clavier pour donner le focus au composant |
alt | contient le texte alternatif pour les composants non textuels |
border | contient la taille de la bordure en pixel |
disabled | permet de désactiver le composant |
maxlength | contient le nombre maximum de caractères saisis |
readonly | permet de rendre une zone de saisie en lecture seule |
rows | contient le nombre de lignes visibles pour une zone de saisie multiligne |
shape | contient la définition d'une région |
size | contient la taille de la zone de saisie |
style | contient le style CSS à utiliser |
target | contient le nom de la frame cible pour l'affichage de la page |
title | contient le titre du composant généralement transformé en une bulle d'aide |
width | contient la largeur du composant |
Le rôle de la plupart de ces tags est identique à leurs homologues définis dans HTML 4.0.
L'attribut style permet de définir un style CSS qui sera appliqué au composant. Cet attribut contient directement la définition du style à la différence de l'attribut styleClass qui contient le nom d'une classe CSS définie dans une feuille de style. Il est préférable d'utiliser l'attribut styleClass plutôt que l'attribut style afin de faciliter la maintenance de la charte graphique.
Exemple : |
<h:outputText value="#{login.nom}" style="color:red;"/>
Les attributs liés à JavaScript sont :
Attribut | Rôle |
onblur | perte du focus |
onchange | changement de la valeur |
onclick | clic du bouton de la souris sur le composant |
ondblclick | double-clic du bouton de la souris sur le composant |
onfocus | réception du focus |
onkeydown | une touche est enfoncée |
onkeypress | appui sur une touche |
onkeyup | une touche est relachée |
onmousedown | le bouton de la souris est enfoncé |
onmousemove | déplacement du curseur de la souris sur le composant |
onmouseout | déplacement du cuseur de la souris hors du composant |
onmouseover | passage de la souris au-dessus du composant |
onmouseup | le bouton de la souris est relaché |
onreset | réinitialisation du formulaire |
onselect | sélection du texte dans une zone de saisie |
onsubmit | soumission du formulaire |
Ce tag représente un formulaire HTML.
Il possède les attributs suivants :
Attribut | Rôle |
binding, id, rendered, styleClass | attributs communs de base |
accept, acceptcharset, dir, enctype, lang, style, target, title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onreset, onsubmit | attributs communs liés aux événements JavaScript |
Il est préférable de définir explicitement l'attribut id pour permettre son exploitation notamment dans le code JavaScript, sinon un id est généré automatiquement.
Ceci est d'autant plus important que les id des composants intégrés dans le formulaire sont préfixés par l'id du formulaire suivi du caractère deux-points. Il faut tenir compte de ce point lors de l'utilisation de code JavaScript faisant référence à un composant.
Ces trois tags permettent de générer des composants pour la saisie de données.
Les attributs de ces tags sont les suivants :
Attribut | Rôle |
cols | définir le nombre de colonnes (pour le composant inputTextarea uniquement) |
immediate | permettre de demander d'ignorer les étapes de validation des données |
redisplay | permettre de réafficher le contenu lors du réaffichage de la page (pour le composant inputSecret uniquement) |
required | rendre obligatoire la saisie d'une valeur |
rows | définir le nombre de lignes affichées (pour le composant inputTextarea uniquement) |
valueChangeListener | préciser une classe de type listener lors du changement de la valeur |
binding, converter, id, rendered, required, styleClass, value, validator | attributs communs de base |
accesskey, alt, dir, disabled,lang, maxlength, readonly, size, style, tabindex, title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onselect | attributs communs liés aux événements JavaScript |
Exemple : |
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head>
<title>Saisie des données</title>
</head>
<body>
<h:form>
<h3>Saisie des données</h3>
<p><h:inputText size="20" /></p>
<p><h:inputTextarea rows="3" cols="20" /></p>
<p><h:inputSecret size="20" /></p>
</h:form>
</body>
</f:view>
</html>
Résultat
Ces deux tags permettent d'insérer dans la vue une valeur sous la forme d'une chaîne de caractères. Par défaut, ils ne génèrent pas de tags HTML mais insèrent simplement la valeur dans la vue sauf si un style CSS est précisé avec l'attribut style ou styleClass. Dans ce cas, la valeur est contenue dans un tag HTML <span>.
Les attributs de ces deux tags sont :
Attribut | Rôle |
escape | booléen qui précise si certains caractères de la valeur seront encodés ou non. La valeur par défaut est false. |
binding, converter, id, rendered, styleClass, value | attributs communs de base |
style, title | attributs communs liés à HTML |
L'attribut escape est particulièrement utile pour encoder certains caractères spéciaux avec leur code HTML correspondant.
Exemple : |
<h:outputText escape="true" value="Nombre d'occurrences > 200"/>
Le tag outputText peut être utilisé pour générer du code HTML en valorisant l'attribut escape à false.
Exemple : |
<p><h:outputText escape="false" value="<H2>Saisie des données</H2>"/></p>
<p><h:outputText escape="true" value="<H2>Saisie des données</H2>"/></p>
Résultat :
Le tag outputFormat permet de formater une chaîne de caractères avec des valeurs fournies en paramètres.
Exemple : |
<p>
<h:outputFormat value="La valeur doit être entre {0} et {1}.">
<f:param value="1"/>
<f:param value="9"/>
</h:outputFormat>
</p>
Résultat :
Ce composant utilise la classe java.text.MessageFormat pour formater le message. L'attribut value doit donc contenir une chaîne de caractères utilisable par cette classe.
Le tag <param> permet de fournir la valeur de chacun des paramètres.
Ce composant représente une image : il génère un tag HTML <img>.
Les attributs de ce tag sont les suivants :
Attribut | Rôle |
binding, id, rendered, styleClass, value | Attributs communs de base |
alt, dir, height, ismap, lang, longdesc, style, title, url, usemap, width | Attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup | Attributs communs liés aux événements JavaScript |
Les attributs value et URL peuvent préciser l'URL de l'image.
Exemple : |
<p><h:graphicImage value="/images/erreur.jpg" /></p>
<p><h:graphicImage url="/images/warning.jpg" /></p>
Résultat :
Ce composant représente un champ caché dans un formulaire.
Les attributs sont les suivants :
Attribut | Rôle |
binding, converter, id, immediate, required, validator, value, valueChangeListener | attributs communs de base |
Exemple : |
<h:inputHidden value="#{login.nom}" />
Résultat dans le code HTML: |
...
<input type="hidden" name="_id0:_id12" value="test" />
...
Ces composants représentent respectivement un bouton de formulaire et un lien qui déclenche une action. L'action demandée sera traitée par le framework JSF.
Les attributs sont les suivants :
Attribut | Rôle |
action | peut être une chaîne de caractères ou une méthode qui renvoie une chaîne de caractères qui sera traitée par le navigation handler. |
actionListener | précise une méthode possédant une signature void nomMethode(ActionEvent) qui sera exécutée lors d'un clic |
image | URL tenant compte du contexte de l'application pour l'image qui sera utilisée à la place du bouton (uniquement pour le tag commandButton) |
type | type de bouton généré : button, submit, reset (uniquement pour le tag commandButton) |
value | le texte affiché par le bouton ou le lien |
accesskey, alt, binding, id, lang, rendered, styleClass | attributs communs de base |
coords (uniquement pour le tag commandLink), dir, disabled, hreflang (uniquement pour le tag commandLink), lang, readonly, rel (uniquement pour le tag commandLink), rev (uniquement pour le tag commandLink), shape (uniquement pour le tag commandLink), style, tabindex, target (uniquement pour le tag commandLink), title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | attributs communs liés aux événements JavaScript |
Il est possible d'insérer dans le corps du tag <commandLink> d'autres composants qui feront partie intégrante du lien comme par exemple du texte ou une image.
Exemple : |
<p>
<h:commandLink>
<h:outputText value="Valider"/>
</h:commandLink>
</p>
<p>
<h:commandLink>
<h:graphicImage value="/images/oeil.jpg"/>
</h:commandLink>
</p>
Résultat :
Il est aussi possible de fournir un ou plusieurs paramètres qui seront envoyés dans la requête en utilisant le tag <param> dans le corps du tag
Exemple : |
<h:commandLink>
<h:outputText value="Selectionner"/>
<f:param name="id" value="1"/>
</h:commandLink>
Résultat dans le page HTML générée : |
<a href="#" onclick="document.forms['_id0']['_id0:_idcl'].value='_id0:_id15';
document.forms['_id0'].submit(); return false;">
mg src="/test_JSF/images/oeil.jpg" alt="" /></a>
Le tag <commandLink> génère du code JavaScript dans la vue pour soumettre le formulaire lors d'un clic.
Ce composant représente un lien direct vers une ressource dont la demande ne sera pas traitée par le framework JSF.
Les attributs sont les suivants :
Attribut | Rôle |
accesskey, binding, converter, id, lang, rendered, styleClass, value | attributs communs de base |
charset, coords, dir, hreflang, lang, rel, rev, shape, style, tabindex, target, title, type | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup | attributs communs liés aux événements JavaScript |
L'attribut value doit contenir l'URL qui sera utilisée dans l'attribut href du lien HTML. Si le premier caractère est un # (dièse) alors le lien pointe vers une ancre définie dans la même page.
Il est possible d'insérer dans le corps du tag ouputLink d'autres composants qui feront partie intégrante du lien.
Exemple : |
<p>
<h:outputLink value="http://java.sun.com">
<h:graphicImage value="/images/java.jpg"/>
</h:outputLink>
</p>
Résultat :
Le code HTML généré dans la page est le suivant :
<a href="http://java.sun.com"><img src="/test_JSF/images/java.jpg" alt="" /></a>
Attention, pour mettre du texte dans le corps du tag, il est nécessaire d'utiliser un tag verbatim ou outputText.
Exemple : |
<p>
<h:outputLink value="http://java.sun.com" title="Java">
<f:verbatim>
Site Java de Sun
</f:verbatim>
</h:outputLink>
</p>
Ces composants représentent respectivement une case à cocher et un ensemble de cases à cocher.
Les attributs sont les suivants :
Attribut | Rôle |
disabledClass | classe CSS pour les éléments non sélectionnés (pour le tag selectManyCheckbox uniquement) |
enabledClass | classe CSS pour les éléments sélectionnés (pour le tag selectManyCheckbox uniquement) |
layout | préciser la disposition des éléments
(pour le tag selectManyCheckbox uniquement) |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | attributs communs de base |
accesskey, border, dir, disabled, lang, readonly, style, tabindex, title | attributs communs liés à HTML (border pour le tag selectManyCheckbox uniquement) |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | attributs communs liés aux événements JavaScript |
L'attribut layout permet de préciser la disposition des cases à cocher : lineDirection pour une disposition horizontale (c'est la valeur par défaut) et pageDirection pour une disposition verticale.
Le tag <selectBooleanCheckbox> dont la valeur peut être associée à une propriété booléenne d'un bean représente une case à cocher simple.
Exemple : |
<h:selectBooleanCheckbox value="#{saisieOptions.recevoirLettre}">
</h:selectBooleanCheckbox> Recevoir la lettre d'information
Résultat :
Pour gérer l'état du composant, il faut utiliser l'attribut value en lui fournissant la valeur d'une propriété booléen d'un backing bean.
Exemple : |
public class SaisieOptions {
private boolean recevoirLettre;
public void setRecevoirLettre(boolean valeur) {
recevoirLettre = valeur;
}
public boolean getRecevoirLettre() {
return recevoirLettre;
}
...
Le tag <selectManyCheckbox> représente un ensemble de cases à cocher. Dans cet ensemble, il est possible d'en sélectionner une ou plusieurs.
Chaque case à cocher est définie par un tag selectItem dans le corps du tag selectManyCheckbox.
Exemple : |
<h:selectManyCheckbox layout="pageDirection">
<f:selectItem itemValue="petit" itemLabel="Petit" />
<f:selectItem itemValue="moyen" itemLabel="Moyen" />
<f:selectItem itemValue="grand" itemLabel="Grand" />
<f:selectItem itemValue="tresgrand" itemLabel="Tres grand" />
</h:selectManyCheckbox>
Résultat :
Le rendu du composant est un tableau HTML dont chaque cellule contient une case à cocher encapsulée dans un tag HTML <label> :
Exemple : |
<table>
<tr>
<td>
<label><input name="_id0:_id1" value="petit" type="checkbox"> Petit</input></label></td>
</tr>
<tr>
<td>
<label><input name="_id0:_id1" value="moyen" type="checkbox"> Moyen</input></label></td>
</tr>
<tr>
<td>
<label><input name="_id0:_id1" value="grand" type="checkbox"> Grand</input></label></td>
</tr>
<tr>
<td>
<label><input name="_id0:_id1" value="tresgrand" type="checkbox"> Tres grand</input>
</label></td>
</tr>
</table>
Ce composant représente un ensemble de boutons radio dont un seul peut être sélectionné.
Les attributs sont les suivants :
Attribut | Rôle |
disabledClass | classe CSS pour les éléments non sélectionnés |
enabledClass | classe CSS pour les éléments sélectionnés |
layout | préciser la disposition des éléments |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | Attributs communs de base |
accesskey, border, dir, disabled, lang, readonly, style, tabindex, title | Attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | Attributs communs liés aux événements JavaScript |
Les éléments peuvent être précisés un par un avec le tag <selectItem>.
Exemple : |
<h:selectOneRadio layout="pageDirection">
<f:selectItem itemValue="petit" itemLabel="Petit" />
<f:selectItem itemValue="moyen" itemLabel="Moyen" />
<f:selectItem itemValue="grand" itemLabel="Grand" />
<f:selectItem itemValue="tresgrand" itemLabel="Tres grand" />
</h:selectOneRadio>
Résultat :
Le rendu du composant est un tableau HTML dont chaque cellule contient un bouton radio encapsulé dans un tag HTML <label> :
Exemple : |
<table>
<tr>
<td>
<label><input type="radio" name="_id0:_id1" value="petit"> Petit</input></label></td>
</tr>
<tr>
<td>
<label><input type="radio" name="_id0:_id1" value="moyen"> Moyen</input></label></td>
</tr>
<tr>
<td>
<label><input type="radio" name="_id0:_id1" value="grand"> Grand</input></label></td>
</tr>
<tr>
<td>
<label><input type="radio" name="_id0:_id1" value="tresgrand"> Tres grand</input>
</label></td>
</tr>
</table>
Les éléments peuvent être précisés sous la forme d'un tableau de type SelecItem avec le tag <selectItems>.
Exemple : |
<h:selectOneRadio value="#{saisieOptions.taille}" layout="pageDirection" id="taille">
<f:selectItems value="#{saisieOptions.tailleItems}"/>
</h:selectOneRadio>
Dans ce cas, le bean doit contenir au moins deux méthodes : getTaille() pour renvoyer la valeur de l'élément sélectionné et getTailleItems() qui renvoie un tableau d'objets de type SelectItems contenant les éléments.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.model.*;
public class SaisieOptions {
private Integer taille = null;
private SelectItem[] tailleItems = {
new SelectItem(new Integer(1), "Petit"),
new SelectItem(new Integer(2), "Moyen"),
new SelectItem(new Integer(3), "Grand"),
new SelectItem(new Integer(4), "Très grand") };
public SaisieOptions() {
taille = new Integer(2);
}
public Integer getTaille() {
return taille;
}
public void setTaille(Integer newValue) {
taille = newValue;
}
public SelectItem[] getTailleItems() {
return tailleItems;
}
}
Le bean doit être déclaré dans le fichier faces-config.xml
Exemple : |
<managed-bean>
<managed-bean-name>saisieOptions</managed-bean-name>
<managed-bean-class>fr.jmdoudoux.dej.jsf.SaisieOptions</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Résultat :
Ce composant représente une liste d'éléments dont un seul peut être sélectionné.
Les attributs sont les suivants :
Attribut | Rôle |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | attributs communs de base |
accesskey, dir, disabled, lang, readonly, style, size, tabindex, title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | attributs communs liés aux événements JavaScript |
L'attribut size permet de préciser le nombre d'éléments de la liste affichée.
Exemple : |
<h:selectOneListbox value="#{saisieOptions.taille}">
<f:selectItems value="#{saisieOptions.tailleItems}"/>
</h:selectOneListbox>
Résultat :
Ce composant représente une liste d'éléments dont plusieurs peuvent être sélectionnés.
Les attributs sont les suivants :
Attribut | Rôle |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | attributs communs de base |
accesskey, dir, disabled, lang, readonly, style, size, tabindex, title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | attributs communs liés aux événements JavaScript |
Exemple : |
<h:selectManyListbox value="#{saisieOptions.legumes}">
<f:selectItems value="#{saisieOptions.legumesItems}"/>
</h:selectManyListbox>
La liste des éléments sélectionnés doit pouvoir contenir zéro ou plusieurs valeurs sous la forme d'un tableau ou d'une liste.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.model.*;
public class SaisieOptions {
private String[] legumes = {
"navets", "choux" };
private SelectItem[] legumesItems = {
new SelectItem("epinards", "Epinards"),
new SelectItem("poireaux", "Poireaux"),
new SelectItem("navets", "Navets"),
new SelectItem("flageolets", "Flageolets"),
new SelectItem("choux", "Choux"),
new SelectItem("aubergines", "Aubergines") };
public SaisieOptions() {
}
public String[] getLegumes() {
return legumes;
}
public SelectItem[] getLegumesItems() {
return legumesItems;
}
}
Résultat :
Il est possible d'utiliser un objet de type List à la place des tableaux.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.model.*;
import java.util.*;
public class SaisieOptions {
private List legumes = null;
private List legumesItems = null;
public List getLegumesItems() {
if (legumesItems == null) {
legumesItems = new ArrayList();
legumesItems.add(new SelectItem("epinards", "Epinards"));
legumesItems.add(new SelectItem("poireaux", "Poireaux"));
legumesItems.add(new SelectItem("navets", "Navets"));
legumesItems.add(new SelectItem("flageolets", "Flageolets"));
legumesItems.add(new SelectItem("choux", "Choux"));
legumesItems.add(new SelectItem("aubergines", "Aubergines"));
}
return legumesItems;
}
public List getLegumes() {
return legumes;
}
public void setLegumes(List newValue) {
legumes = newValue;
}
public SaisieOptions() {
legumes = new ArrayList();
legumes.add("navets");
legumes.add("choux");
}
}
Ce composant représente une liste déroulante dont un seul élément peut être sélectionné.
Les attributs sont les suivants :
Attribut | Rôle |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | attributs communs de base |
accesskey, dir, disabled, lang, readonly, style, tabindex, title | attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | attributs communs liés aux événements JavaScript |
Exemple : |
<h:selectOneMenu value="#{saisieOptions.taille}">
<f:selectItems value="#{saisieOptions.tailleItems}"/>
</h:selectOneMenu>
Résultat :
Exemple : le code HTML généré |
<select name="_id0:_id6" size="1"> <option value="1">Petit</option>
<option value="2" selected="selected">Moyen</option>
<option value="3">Grand</option>
<option value="4">Très grand</option>
</select>
Ce composant représente une liste d'éléments dont le rendu HTML est un tag select avec une seule option visible.
Les attributs sont les suivants :
Attribut | Rôle |
binding, converter, id, immediate, styleClass, required, rendered, validator, value, valueChangeListener | Attributs communs de base |
accesskey, dir, disabled, lang, readonly, style, tabindex, title | Attributs communs liés à HTML |
onblur, onchange, onclick, ondblclick, onfocus, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup, onselect | Attributs communs liés aux événements JavaScript |
Exemple : |
<h:selectManyMenu value="#{saisieOptions.legumes}">
<f:selectItems value="#{saisieOptions.legumesItems}"/>
</h:selectManyMenu>
Résultat :
Des messages peuvent être émis lors de traitements. Ils sont stockés dans le contexte de l'application JSF pour être restitués dans la vue. Ils permettent notamment de fournir des messages d'erreurs aux utilisateurs.
JSF définit quatre types de messages :
Chaque message possède un résumé et un descriptif.
Le tag <messages> permet d'afficher tous les messages stockés dans le contexte de l'application JSF.
Le tag message permet d'afficher un seul message, le dernier ajouté, pour un composant donné.
Les attributs sont les suivants :
Attributs | Rôle |
errorClass | nom d'une classe CSS pour un message de type error |
errorStyle | style CSS pour un message de type error |
fatalClass | nom d'une classe CSS pour un message de type fatal |
fatalStyle | style CSS pour un message de type fatal |
globalOnly | booléen qui permet de n'afficher que les messages qui ne sont pas associés à un composant. Par défaut, False (uniquement pour le tag messages) |
infoClass | nom d'une classe CSS pour un message de type Information |
infoStyle | style CSS pour un message de type Information |
Layout | format de la liste de messages : list ou table (uniquement pour le composant messages) |
showDetail | booléen qui précise si la description des messages est affichée ou non. Par défaut, false pour le tag message et true pour le tag messages |
showSummary | booléen qui précise si le résumé des messages est affiché ou non. Par défaut, true pour le tag message et false pour le tag messages |
Tooltip | booléen qui précise si la description est affichée sous la forme d'une bulle d'aide |
warnClass | nom d'une classe CSS pour un message de type Warning |
warnStyle | le style CSS pour un message de type Warning |
For | l'identifiant du composant pour lequel le message doit être affiché |
binding, id, rendered, styleClass | attributs communs de base |
style, title | attributs communs liés à HTML |
Ce composant permet de regrouper plusieurs composants.
Les attributs sont les suivants :
Attribut | Rôle |
binding, id, rendered, styleClass | attributs communs de base |
style | style CSS |
Exemple : |
<td bgcolor='#DDDDDD'>
<h:panelGroup>
<h:inputText value="#{login.nom}" id="nom" required="true"/>
<h:message for="nom"/>
</h:panelGroup>
</td>
Résultat :
Ce composant représente un tableau HTML.
Les attributs sont les suivants :
Attribut | Rôle |
bgcolor | couleur de fond du tableau |
border | taille de la bordure du tableau |
cellpadding | espacement intérieur de chaque cellule |
cellspacing | espacement extérieur de chaque cellule |
columnClasses | nom de classes CSS pour les colonnes. Il est possible de préciser plusieurs noms de classes qui seront utilisées sur chaque colonne |
columns | nombre de colonnes du tableau |
footerClass | nom de la classe CSS pour le pied du tableau |
frame | précise les règles pour le contour du tableau. Les valeurs possibles sont : none, above, below, hsides, vsides, lhs, rhs, box, border |
headerClass | nom de la classe CSS pour l'en-tête du tableau |
rowClasses | nom de classes CSS pour les lignes. Il est possible de préciser deux noms de classes séparés par une virgule qui seront utilisés alternativement sur chaque ligne |
rules | précise les règles de dessin des lignes entre les cellules. Les valeurs possibles sont : groups, rows, columns, all |
summary | résumé du tableau |
binding, id, rendered, styleClass, value | attributs communs de base |
dir, lang, style, title, width | attributs communs liés à HTML |
onclick, ondblclick, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup | attributs communs liées aux événements JavaScript |
Par défaut les composants sont insérés les uns à la suite des autres dans les cellules en partant de la gauche vers la droite et en passant à la ligne suivante si nécessaire.
Il est possible de ne mettre qu'un seul composant par cellule. Ainsi pour placer plusieurs composants dans une cellule, il faut les regrouper dans un tag panelGroup.
Exemple : |
<h:panelGrid columns="2">
<h:outputText value="Nom : " />
<h:panelGroup>
<h:inputText value="#{login.nom}" id="nom" required="true"/>
<h:message for="nom"/>
</h:panelGroup>
<h:outputText value="Mot de passe :" />
<h:inputSecret value="#{login.mdp}"/>
<h:commandButton value="Login" action="login"/>
</h:panelGrid>
Résultat :
Le code HTML généré est le suivant :
Exemple : le code HTML généré |
...
<table>
<tbody>
<tr>
<td>Nom : </td>
<td><input id="_id0:nom" type="text" name="_id0:nom" /></td>
</tr>
<tr>
<td>Mot de passe :</td>
<td><input type="password" name="_id0:_id6" value="" /></td>
</tr>
<tr>
<td><input type="submit" name="_id0:_id7" value="Login" /></td>
</tr>
</tbody>
</table>
...
Ce composant représente un tableau HTML dans lequel des données vont pouvoir être automatiquement présentées. Ce composant est sûrement le plus riche en fonctionnalité et donc le plus complexe des composants fournis en standard.
Les attributs sont les suivants :
Attribut | Rôle |
bgcolor | couleur de fond du tableau |
border | taille de la bordure du tableau |
cellpadding | espacement intérieur de chaque cellule |
cellspacing | espacement extérieur de chaque cellule |
columnClasses | nom de classes CSS pour les colonnes. Il est possible de préciser plusieurs noms de classes qui seront utilisées sur chaque colonne |
first | index de la première occurrence des données qui sera affichée dans le tableau |
footerClass | nom de la classe CSS pour le pied du tableau |
frame | précise les règles pour le contour du tableau. Les valeurs possibles sont : none, above, below, hsides, vsides, lhs, rhs, box, border |
headerClass | nom de la classe CSS pour l'en-tête du tableau |
rowClasses | nom de classes CSS pour les lignes. Il est possible de préciser deux noms de classes qui seront utilisées alternativement sur chaque ligne |
rules | précise les règles de dessin des lignes entre les cellules. Les valeurs possibles sont : groups, rows, columns, all |
summary | résumé du tableau |
var | nom de la variable qui va contenir l'occurrence en cours de traitement lors du parcours des données |
binding, id, rendered, styleClass, value | attributs communs de base |
dir, lang, style, title, width | attributs communs liés à HTML |
onclick, ondblclick, onkeydown, onkeypress, onkeyup, onmousedown, onmousemove, onmouseout, onmouseover, onmouseup | attributs communs liés aux événements JavaScript |
Le tag <dataTable> parcourt les données et pour chaque occurrence, il crée une ligne dans le tableau.
L'attribut value représente une expression qui précise les données à utiliser. Ces données peuvent être sous la forme :
Pour chaque élément encapsulé dans les données, le tag dataTable crée une nouvelle ligne.
Quelque soit le type qui encapsule les données, le composant dataTable va les mapper dans un objet de type DataModel. C'est cet objet que le composant va utiliser comme source de données. JSF définit 5 classes qui héritent de la classe DataModel : ArrayDataModel, ListDataModel, ResultDataModel, ResultSetDataModel et ScalarDataModel.
La méthode getWrappedObject() permet d'obtenir la source de données fournie en paramètre de l'attribut value.
L'attribut item permet de préciser le nom d'une variable qui va contenir les données d'une occurrence.
Chaque colonne est définie grâce à un tag <column>.
Exemple : |
<h:dataTable value="#{listePersonnes.personneItems}" var="personne" cellspacing="4">
<h:column>
<f:facet name="header">
<h:outputText value="Nom" />
</f:facet>
<h:outputText value="#{personne.nom}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Prenom" />
</f:facet>
<h:outputText value="#{personne.prenom}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Date de naissance" />
</f:facet>
<h:outputText value="#{personne.datenaiss}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Poids" />
</f:facet>
<h:outputText value="#{personne.poids}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Taille" />
</f:facet>
<h:outputText value="#{personne.taille}"/>
</h:column>
</h:dataTable>
L'en-tête et le pied du tableau sont précisés avec un tag <facet> pour chacun dans chaque tag <column>.
Dans l'exemple précédent l'instance listePersonnes est une classe dont le code est le suivant :
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
public class PersonnesBean {
private List PersonneItems = null;
public List getPersonneItems() {
if (PersonneItems == null) {
PersonneItems = new ArrayList();
PersonneItems.add(new Personne("Nom1", "Prenom1",
new GregorianCalendar(1967, Calendar.OCTOBER, 22).getTime(),10,1.10f));
PersonneItems.add(new Personne("Nom2", "Prenom2",
new GregorianCalendar(1972, Calendar.MARCH, 10).getTime(),20,1.20f));
PersonneItems.add(new Personne("Nom3", "Prenom3",
new GregorianCalendar(1944, Calendar.NOVEMBER, 4).getTime(),30,1.30f));
PersonneItems.add(new Personne("Nom4", "Prenom4",
new GregorianCalendar(1958, Calendar.JULY, 19).getTime(),40,1.40f));
PersonneItems.add(new Personne("Nom5", "Prenom5",
new GregorianCalendar(1934, Calendar.JANUARY, 6).getTime(),50,1.50f));
PersonneItems.add(new Personne("Nom6", "Prenom6",
new GregorianCalendar(1989, Calendar.DECEMBER, 12).getTime(),60,1.60f));
}
return PersonneItems;
}
}
La méthode getPersonneItems() renvoie une collection d'objets de type Personne.
La classe Personne encapsule simplement les données d'une personne.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.Date;
public class Personne {
private String nom;
private String prenom;
private Date datenaiss;
private int poids;
private float taille;
private boolean supprime;
public Personne(String nom, String prenom, Date datenaiss, int poids,
float taille) {
super();
this.nom = nom;
this.prenom = prenom;
this.datenaiss = datenaiss;
this.poids = poids;
this.taille = taille;
this.supprime = false;
}
public boolean isSupprime() {
return supprime;
}
public void setSupprime(boolean supprimer) {
supprime = supprimer;
}
public Date getDatenaiss() {
return datenaiss;
}
public void setDatenaiss(Date datenaiss) {
this.datenaiss = datenaiss;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public int getPoids() {
return poids;
}
public void setPoids(int poids) {
this.poids = poids;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
public float getTaille() {
return taille;
}
public void setTaille(float taille) {
this.taille = taille;
}
}
Il est très facile de préciser un style particulier pour des lignes paires et impaires.
Il suffit de définir les deux styles désirés.
Exemple dans la partie en-tête de la JSP : |
<STYLE type="text/css">
<!--
.titre {
background-color:#000000;
color:#FFFFFF;
}
.paire {
background-color:#EFEFEF;
}
.impaire {
background-color:#CECECE;
}
-->
</STYLE>
Il suffit d'utiliser les attributs headerClass, footerClass, rowClasses ou columnClasses. Avec ces deux derniers attributs, il est possible de préciser plusieurs styles séparés par une virgule pour définir l'apparence de chacune des lignes de façon répétitive.
Exemple : |
<h:dataTable value="#{listePersonnes.personneItems}" var="personne"
cellspacing="4" width="60%" rowClasses="paire,impaire" headerClass="titre">
Résultat :
Les éléments du tableau peuvent par exemple être sélectionnés grâce à une case à cocher pour permettre de réaliser des traitements sur les éléments marqués.
Il suffit de rajouter dans l'exemple précédent une colonne contenant une case à cocher et, sous le tableau, un bouton qui va réaliser les traitements sur les éléments cochés.
Exemple dans la JSP : |
...
<h:form>
<h1>Test</H1>
<div align="center">
<h:dataTable value="#{listePersonnes.personneItems}" var="personne"
cellspacing="4" width="60%" rowClasses="paire,impaire" headerClass="titre">
...
<h:column>
<f:facet name="header">
<h:outputText value="Sélection"/>
</f:facet>
<h:selectBooleanCheckbox value="#{personne.supprime}" />
</h:column>
</h:dataTable>
<p>
<h:commandButton value="Supprimer les sélectionnés"
action="#{listePersonnes.supprimer}"/>
</p>
</div>
</h:form>
...
Il reste alors à ajouter les traitements dans la méthode supprimer() de la classe PersonnesBean qui sera appelée lors d'un clic sur le bouton « Supprimer les sélectionnés ».
Exemple : |
public class PersonnesBean {
...
public String supprimer() {
Iterator iterator = personneItems.iterator();
Personne pers=null;
while (iterator.hasNext()) {
pers = (Personne) iterator.next();
System.out.println("nom="+pers.getNom()+" "+pers.isSupprime());
// ajouter les traitements utiles
}
return null;
}
}
Un clic sur le bouton « Supprimer les sélectionnés » affiche dans la console, la liste des éléments avec l'état de la case à cocher.
Exemple : |
nom=Nom1 false
nom=Nom2 true
nom=Nom3 false
nom=Nom4 true
nom=Nom5 false
nom=Nom6 true
Les données sont stockées dans un ou plusieurs JavaBeans qui encapsulent les différentes données des composants.
Ces données possèdent deux représentations :
Chaque objet de type Renderer possède une représentation par défaut des données. La transformation d'une représentation en une autre est assurée par des objets de type Converter. JSF fournit en standard plusieurs objets de type Converter mais il est aussi possible de développer ses propres objets.
JSF propose en standard un mécanisme de conversion des données. Celui-ci repose sur un ensemble de classes dont certaines sont fournies en standard pour des conversions de base. Il est possible de définir ses propres classes de conversion pour répondre à des besoins spécifiques.
Ces conversions sont nécessaires car toutes les données transmises et affichées le sont sous la forme de chaînes de caractères. Cependant, leur exploitation dans les traitements nécessite souvent qu'elles soient stockées dans un autre format pour être exploitées : un exemple flagrant est une donnée de type date.
Toutes les données saisies par l'utilisateur sont envoyées dans la requête http sous la forme de chaînes de caractères. Chacune de ces valeurs est désignée par « request value » dans les spécifications de JSF.
Ces valeurs sont stockées dans leurs composants respectifs dans des champs désignés par « submitted value » dans les spécifications.
Ces valeurs sont éventuellement converties implicitement ou explicitement et sont stockées dans leurs composants respectifs dans des champs désignés par « local value ». Ensuite, ces données sont éventuellement validées.
L'intérêt d'un tel procédé est de s'assurer que les données seront valides avant de pouvoir les utiliser dans les traitements. Si la conversion ou la validation échoue, les traitements du cyle de vie de la page sont arrêtés et la page est réaffichéeen montrant les messages d'erreurs. Sinon la phase de mise à jour des données ( « Update model values » ) du modèle est exécutée.
Les spécifications JSF imposent l'implémentation des convertisseurs suivants : javax.faces.DateTime, javax.faces.Number, javax.faces.Boolean, javax.faces.Byte, javax.faces.Character, javax.faces.Double, javax.faces.Float, javax.faces.Integer, javax.faces.Long, javax.faces.Short, javax.faces.BigDecimal et javax.faces.BigInteger.
JSF effectue une conversion implicite des données lorsque celles-ci correspondent à un type primitif, à BigDecimal ou BigInteger en utilisant les convertisseurs appropriés.
Deux convertisseurs sont proposés en standard pour mettre en oeuvre des conversions qui ne correspondent pas à des types primitifs :
Ce tag permet d'ajouter à un composant un convertisseur de valeur numérique.
Ce tag possède les attributs suivants :
Attributs | Rôle |
type | type de valeur. Les valeurs possibles sont number (par défaut), currency et percent |
pattern | motif de formatage qui sera utilisé par une instance de java.text.DecimalFormat |
maxFractionDigits | nombre maximum de chiffres composant la partie décimale |
minFractionDigits | nombre minimum de chiffres composant la partie décimale |
maxIntegerDigits | nombre maximum de chiffres composant la partie entière |
minIntegerDigits | nombre minimum de chiffres composant la partie entière |
integerOnly | booléen qui précise si uniquement la partie entière est prise en compte (false par défaut) |
groupingUsed | booléen qui précise si le séparateur de groupe d'unité est utilisé (true par défaut) |
locale | objet de type java.util.Locale permettant de définir la Locale à utiliser pour les conversions |
currencyCode | code de la monnaie utilisée pour la conversion |
currencySymbol | symbole de la monnaie utilisée pour la conversion |
Exemple : |
<p>valeur1 = <h:outputText value="#{convert.prix}">
<f:convertNumber type="currency"/>
</h:outputText>
</p>
<p>valeur2 = <h:outputText value="#{convert.poids}">
<f:convertNumber type="number"/>
</h:outputText>
</p>
<p>valeur3 = <h:outputText value="#{convert.ratio}">
<f:convertNumber type="percent"/>
</h:outputText>
</p>
<p>valeur4 = <h:outputText value="#{convert.prix}">
<f:convertNumber integerOnly="true" maxIntegerDigits="2"/>
</h:outputText>
</p>
<p>valeur5 = <h:outputText value="#{convert.prix}">
<f:convertNumber pattern="#.##"/>
</h:outputText>
</p>
Le code du bean utilisé dans cet exemple est le suivant :
Exemple : |
package fr.jmdoudoux.dej.jsf;
public class Convert {
private int poids;
private float prix;
private float ratio;
public Convert() {
super();
this.poids = 12345;
this.prix = 1234.56f ;
this.ratio = 0.12f ;
}
public int getPoids() {
return poids;
}
public void setPoids(int poids) {
this.poids = poids;
}
public float getRatio() {
return ratio;
}
public void setRatio(float ratio) {
this.ratio = ratio;
}
public float getPrix() {
return prix;
}
public void setPrix(float prix) {
this.prix = prix;
}
}
Ce tag permet d'ajouter à un composant un convertisseur de valeurs temporelles.
Ce tag possède les attributs suivants :
Attributs | Rôle |
Type | type de valeur. Les valeurs possibles sont date (par défaut), time et both |
dateStyle | style prédéfini de la date. Les valeurs possibles sont short, medium, long, full ou default |
timeStyle | style prédéfini de l'heure. Les valeurs possibles sont short, medium, long, full ou default |
Pattern | motif de formatage qui sera utilisé par une instance de java.text.SimpleDateFormat |
Locale | objet de type java.util.Locale permettant de définir la locale à utiliser pour les conversions |
timeZone | objet de type java.util.TimeZone utilisé lors des conversions |
Exemple : |
<p>Date1 = <h:outputText value="#{convertDate.dateNaiss}">
<f:convertDateTime pattern="MM/yyyy"/>
</h:outputText>
</p>
<p>Date2 = <h:outputText value="#{convertDate.dateNaiss}">
<f:convertDateTime pattern="EEE, dd MMM yyyy"/>
</h:outputText>
</p>
<p>Date3 = <h:outputText value="#{convertDate.dateNaiss}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:outputText>
</p>
<p>Date4 = <h:outputText value="#{convertDate.dateNaiss}">
<f:convertDateTime dateStyle="full"/>
</h:outputText>
</p>
Le code du bean utilisé comme source dans cet exemple est le suivant :
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.Date;
public class ConvertDate {
private Date dateNaiss;
public ConvertDate() {
super();
this.dateNaiss = new Date();
}
public Date getDateNaiss() {
return dateNaiss;
}
public void setDateNaiss(Date dateNaiss) {
this.dateNaiss = dateNaiss;
}
}
Résultat :
Les messages d'erreurs issus de ces conversions peuvent être affichés en utilisant les tag <message> ou <messages>.
Par défaut, ils contiennent une description : « Conversion error occured ».
Pour modifier ce message par défaut ou l'internationaliser, il faut définir une clé javax.faces.component.UIInput.CONVERSION dans le fichier properties de définition des chaînes de caractères.
Exemple : |
javax.faces.component.UIInput.CONVERSION=La valeur saisie n'est pas correctement formatée.
JSF fournit en standard des convertisseurs pour les types primitifs et quelques objets de base. Il peut être nécessaire de développer son propre convertisseur pour des besoins spécifiques.
Pour écrire son propre convertisseur, il faut définir une classe qui implémente l'interface Converter. Cette interface définit deux méthodes :
La méthode getAsObject() doit lever une exception de type ConverterException si une erreur de conversion est détectée dans les traitements.
|
La suite de ce chapitre sera développée dans une version future de ce document
|
JSF propose en standard un mécanisme de validation des données. Celui-ci repose sur un ensemble de classes qui permettent de faire des vérifications standard. Il est possible de définir ses propres classes de validation pour répondre à des besoins spécifiques.
La validation peut se faire de deux façons : au niveau de certains composants ou avec des classes spécialement développées pour des besoins spécifiques. Ces classes sont attachables à un composant et sont réutilisables. Ces validations sont effectuées côté serveur.
Les validators sont enregistrés sur des composants. Ce sont des classes qui utilisent des données pour effectuer des opérations de validation de la valeur des données : contrôle de présence, de type de données, de plage de valeurs, de format, ...
Toutes ces classes implémentent l'interface javax.faces.validator.Validator. JSF propose en standard plusieurs classes pour la validation :
Pour faciliter l'utilisation de ces classes, la bibliothèque de tags personnalisés Core propose des tags dédiés à la mise en oeuvre de ces classes :
Ces trois tags possèdent deux attributs nommés minimum et maximum qui permettent de préciser respectivement la valeur de début et de fin selon le Validator utilisé. L'un, l'autre ou les deux attributs peuvent être utilisés.
L'ajout d'une validation sur un contrôle peut se faire de plusieurs manières :
Pour ajouter une validation à un composant dans la JSP , il suffit d'insérer le tag de validation dans le corps du tag du composant.
Exemple : |
<h:inputText id="nombre" converter="#{Integer}" required="true"
value="#{saisieDonnees.nombre}">
<f:validate_longrange minimum="1" maximum="9" />
</h:inputText>
Certaines implémentations de composants peuvent contenir des validations implicites en fonction du contexte. C'est par exemple le cas du composant <inputText> qui, lorsque son attribut required est à true, effectue un contrôle de présence de données saisies.
Exemple : |
<h:inputText id="nombre" converter="#{Integer}" required="true"
value="#{saisieDonnees.nombre}"/>
Toutes les validations sont faites côté serveur dans la version courante de JSF.
Les messages d'erreurs issus de ces conversions peuvent être affichés en utilisant les tags <message> ou <messages>.
Ils contiennent une description par défaut selon le validator utilisé commençant par « Validation error : ».
Pour modifier ce message par défaut ou l'internationaliser, il faut définir une clé dédiée dans le fichier properties de définition des chaînes de caractères. Les clés définies sont les suivantes :
Dans certains cas, il est nécessaire d'empêcher la validation. Par exemple, dans une page de saisie d'informations disposant d'un bouton « Valider » et « Annuler ». La validation doit être opérée lors d'un clic sur le bouton « Valider » mais ne doit pas l'être lors d'un clic sur le bouton « Annuler ».
Pour chaque composant dont l'action doit être exécutée sans validation, il faut mettre l'attribut immediate du composant à true.
Exemple : |
<h:commandButton value="Annuler" action="annuler" immediate="true"/>
JSF fournit en standard des classes de validation de base. Il peut être nécessaire de développer ses propres classes de validation pour des besoins spécifiques.
Pour écrire sa propre classe de validation, il faut définir une classe qui implémente l'interface javax.faces.validator.Validator. Cette interface définit une seule méthode :
Elle attend en paramètre :
La méthode validate() doit lever une exception de type ValidatorException si une erreur dans les traitements de validation est détectée.
Exemple : |
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
import com.sun.faces.util.MessageFactory;
public class NumeroDeSerieValidator implements Validator {
public static final String CLE_MESSAGE_VALIDATION_IMPOSSIBLE =
"message.validation.impossible";
public void validate(FacesContext contexte, UIComponent composant,
Object objet) throws ValidatorException {
String valeur = null;
boolean estValide = false;
if ((contexte == null) || (composant == null)) {
throw new NullPointerException();
}
if (!(composant instanceof UIInput)) {
return;
}
valeur = objet.toString();
Pattern p = Pattern.compile("[0-9][0-9]-[0-9][0-9][0-9]",Pattern.MULTILINE);
Matcher m = p.matcher(valeur);
estValide = m.matches();
if (!estValide) {
FacesMessage errMsg = MessageFactory.getMessage(contexte,
CLE_MESSAGE_VALIDATION_IMPOSSIBLE);
throw new ValidatorException(errMsg);
}
}
}
Dans l'exemple précédent, la valeur à valider doit respecter une expression régulière de la forme deux chiffres, un tiret et trois chiffres.
Si la validation échoue alors il sera nécessaire d'informer l'utilisateur de la raison de l'échec grâce à un message stocké dans le resourceBundle de l'application.
Exemple : |
message.validation.impossible=Le format du numéro de série est erroné
La valeur du message dans le resourceBundle peut être obtenue en utilisant la méthode getMessage() de la classe MessageFactory. Cette méthode attend en paramètres le contexte JSF de l'application et la clé du ressourceBundle à extraire. Elle renvoie un objet de type FacesMessages. Il suffit de fournir cet objet à la nouvelle instance de la classe ValidatorException.
Pour pouvoir utiliser une classe de validation, il faut la déclarer dans le fichier de configuration.
Exemple : |
<validator>
<validator-id>fr.jmdoudoux.dej.jsf.NumeroDeSerie</validator-id>
<validator-class>fr.jmdoudoux.dej.jsf.NumeroDeSerieValidator</validator-class>
</validator>
Le tag <validator-id> permet de définir un identifiant pour la classe de validation. Le tag <validator-class> permet de préciser la classe pleinement qualifiée.
Pour utiliser la classe de validation dans une page, il faut utiliser le tag <validator> en fournissant à l'attribut validatorId la valeur donnée au tag <validator-id> dans le fichier de configuration :
Exemple : |
<h:panelGrid columns="2">
<h:outputText value="Numéro de série : " />
<h:panelGroup>
<h:inputText value="#{validation.numeroSerie}" id="numeroSerie" required="true">
<f:validator validatorId="fr.jmdoudoux.dej.jsf.NumeroDeSerie"/>
</h:inputText>
<h:message for="numeroSerie"/>
</h:panelGroup>
</h:panelGrid>
La saisie d'un numéro répondant à l'expression régulière et l'appui sur la touche entrée n'affiche aucun message d'erreur :
La saisie d'un numéro ne répondant pas à l'expression régulière affiche le message d'erreur :
Il est possible de définir une méthode dans un bean qui va offrir les services de validation. Cette méthode doit avoir une signature similaire à celle de la méthode validate() de l'interface Validator.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import com.sun.faces.util.MessageFactory;
public class Validation {
public static final String CLE_MESSAGE_VALIDATION_IMPOSSIBLE =
"message.validation.impossible";
private String numeroSerie;
public String getNumeroSerie() {
return numeroSerie;
}
public void setNumeroSerie(String numeroSerie) {
this.numeroSerie = numeroSerie;
}
public void valider(FacesContext contexte, UIComponent composant, Object objet) {
String valeur = null;
boolean estValide = false;
if ((contexte == null) || (composant == null)) {
throw new NullPointerException();
}
if (!(composant instanceof UIInput)) {
return;
}
valeur = objet.toString();
Pattern p = Pattern.compile("[0-9][0-9]-[0-9][0-9][0-9]",Pattern.MULTILINE);
Matcher m = p.matcher(valeur);
estValide = m.matches();
if (!estValide) {
FacesMessage errMsg = MessageFactory.getMessage(contexte,
CLE_MESSAGE_VALIDATION_IMPOSSIBLE);
throw new ValidatorException(errMsg);
}
}
}
Pour utiliser cette méthode, il faut utiliser l'attribut validator et lui fournir en paramètre une expression qui désigne la méthode d'une instance du bean.
Exemple : |
<h:panelGrid columns="2">
<h:outputText value="Numéro de série : " />
<h:panelGroup>
<h:inputText value="#{validation.numeroSerie}" id="numeroSerie"
required="true" validator="#{validation.valider}" />
<h:message for="numeroSerie"/>
</h:panelGroup>
</h:panelGrid>
Cette approche est particulièrement utile pour des besoins spécifiques à une application car sa mise en oeuvre est difficilement portable d'une application à une autre.
De base, le modèle de validation des données proposé par JSF repose sur une validation unitaire de chaque composant. Il est cependant fréquent d'avoir besoin de faire une validation en fonction des données d'un ou plusieurs autres composants.
Pour réaliser ce genre de tâche, il faut créer un backing bean qui aura accès à chacun des composants nécessaires aux traitements et définir dans ce bean une méthode qui va réaliser les traitements de validation.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
import com.sun.faces.util.MessageFactory;
public class Validation {
public static final String CLE_MESSAGE_VALIDATION_IMPOSSIBLE =
"message.validation.impossible";
private String numeroSerie;
private String cle;
private UIInput cleInput;
private UIInput numeroSerieInput;
public String getNumeroSerie() {
return numeroSerie;
}
public void setNumeroSerie(String numeroSerie) {
this.numeroSerie = numeroSerie;
}
public void valider(FacesContext contexte, UIComponent composant, Object objet) {
String valeur = null;
boolean estValide = false;
if ((contexte == null) || (composant == null)) {
throw new NullPointerException();
}
if (!(composant instanceof UIInput)) {
return;
}
valeur = objet.toString();
Pattern p = Pattern.compile("[0-9][0-9]-[0-9][0-9][0-9]",Pattern.MULTILINE);
Matcher m = p.matcher(valeur);
estValide = m.matches();
if (!estValide) {
FacesMessage errMsg = MessageFactory.getMessage(contexte,
CLE_MESSAGE_VALIDATION_IMPOSSIBLE);
throw new ValidatorException(errMsg);
}
}
public void validerCle(FacesContext contexte, UIComponent composant, Object objet) {
System.out.println("validerCle");
String valeurNumero = numeroSerieInput.getLocalValue().toString();
String valeurCle = cleInput.getLocalValue().toString();
boolean estValide = false;
if (contexte == null) {
throw new NullPointerException();
}
Pattern p = Pattern.compile("[0-9][0-9]-[0-9][0-9][0-9]",Pattern.MULTILINE);
Matcher m = p.matcher(valeurNumero);
estValide = m.matches() && valeurCle.equals("789");
System.out.println("estValide="+estValide);
if (!estValide) {
FacesMessage errMsg = MessageFactory.getMessage(contexte,
CLE_MESSAGE_VALIDATION_IMPOSSIBLE);
throw new ValidatorException(errMsg);
}
}
public String getCle() {
return cle;
}
public void setCle(String cle) {
this.cle = cle;
}
public UIInput getCleInput() {
return cleInput;
}
public void setCleInput(UIInput cleInput) {
this.cleInput = cleInput;
}
public UIInput getNumeroSerieInput() {
return numeroSerieInput;
}
public void setNumeroSerieInput(UIInput numeroSerieInput) {
this.numeroSerieInput = numeroSerieInput;
}
}
Il suffit alors d'ajouter un champ caché dans la vue sur lequel la classe de validation sera appliquée.
Exemple : |
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ page language="java" %>
<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en">
<html>
<f:view>
<head>
<title>Tests de validation</title>
</head>
<body bgcolor="#FFFFFF">
<h:form>
<h2>Tests de validation</h2>
<h:panelGrid columns="2">
<h:outputText value="Numéro de série : " />
<h:panelGroup>
<h:inputText value="#{validation.numeroSerie}" id="numeroSerie"
required="true" binding="#{validation.numeroSerieInput}" />
<h:message for="numeroSerie"/>
</h:panelGroup>
<h:outputText value="clé : " />
<h:panelGroup>
<h:inputText value="#{validation.cle}" id="cle" binding="#{validation.cleInput}"
required="true" />
<h:message for="validationCle"/>
</h:panelGroup>
</h:panelGrid>
<h:inputHidden id="validationCle" validator="#{validation.validerCle}" value="nul"/>
<h:commandButton value="Valider" action="submit"/>
</h:form>
</body>
</f:view>
</html>
L'écriture d'un tag personnalisé facilite l'utilisation d'un convertisseur ou d'un validateur et permet de lui fournir des paramètres.
Il faut définir une classe nommée handler qui va contenir les traitements du tag. Cette classe doit hériter d'une sous-classe dédiée selon le type d'élément que va représenter le tag :
Le handler est un bean dont les propriétés doivent correspondre à chaque attribut défini dans le tag.
Pour pouvoir utiliser un tag personnalisé, il faut définir un fichier .tld.
Ce fichier au format XML défini dans les spécifications des JSP permet de fournir des informations sur la bibliothèque de tags personnalisés notamment la version des spécifications utilisées et des informations sur chaque tag.
Enfin, il est nécessaire de déclarer l'utilisation de la bibliothèque de tags personnalisés dans la JSP.
Il faut définir un handler pour le tag qui est un bean héritant de la classe ConverterTag.
Il est important dans le constructeur du handler de faire un appel à la méthode setConverterId() en lui passant un id défini dans le fichier de configuration de l'application JSF.
Il faut redéfinir la méthode release() dont les traitements vont permettre de réinitialiser les propriétés de la classe. Ceci est important lorsque, pour améliorer les performances, on souhaite placer ces objets dans un pool. La méthode release() est dans ce cas utilisée pour recycler les instances du pool non utilisées.
Il faut ensuite redéfinir la méthode createConverter() qui va permettre la création d'une instance du converter en utilisant les éventuels valeurs des attributs du tag.
La valeur fournie à un attribut d'un tag pour être soit un littéral soit une expression dont le contenu devra être évalué au moment de son utilisation.
|
La suite de ce chapitre sera développée dans une version future de ce document
|
L'écriture d'un tag personnalisé pour un validateur suit les mêmes règles que pour un convertisseur. La grande différence est que la classe handler doit hériter de la classe ValidatorTag. La méthode à appeler dans le constructeur est la méthode setValidatorId() et la méthode à redéfinir pour créer une instance du validateur est la méthode createValidator().
|
La suite de ce chapitre sera développée dans une version future de ce document
|
JSF sauvegarde l'état de chaque élément présent dans la vue : les composants, les convertisseurs, les validateurs, ... pourvu que ceux-ci mettent en oeuvre un mécanisme adéquat.
Ces états sont stockés dans un champ de type hidden dans la vue pour permettre leur échange entre deux requêtes si l'application le prévoit dans le fichier de configuration.
Ce mécanisme peut prendre deux formes selon que :
Dans le premier cas, c'est le mécanisme standard de la sérialisation qui sera utilisé. Il nécessite donc très peu voire aucun code particulier si les champs de la classe sont tous d'un type qui est sérialisable.
L'implémentation de l'interface StateHolder nécessite la définition des deux méthodes définies dans l'interface (saveState() et restoreState()) et la présence d'un constructeur par défaut. Cette approche peut être intéressante pour obtenir un contrôle très fin de la sauvegarde et de la restauration de l'état.
La méthode saveState(FacesContext) renvoie un objet sérialisable qui va contenir les données de l'état à sauvegarder. La méthode restoreState(FacesContext, Object) effectue l'opération inverse.
Il est aussi nécessaire de définir une propriété nommée transient de type booléen qui précise si l'état doit être sauvegardé ou non.
Si l'élément n'implémente pas l'interface Serializable ou StateHolder alors son état n'est pas sauvegardé entre deux échanges de la vue.
Une application de type web se compose d'un ensemble de pages dans lequel l'utilisateur navigue en fonction de ses actions.
Un système de navigation standard peut être facilement mis en oeuvre avec JSF grâce à un paramétrage au format XML dans le fichier de configuration de l'application.
Le système de navigation assure la gestion de l'enchaînement des pages en utilisant des actions. Les règles de navigation sont des chaînes de caractères qui sont associées à une page d'origine et qui permettent de déterminer la page de résultat. Toutes ces règles sont contenues dans le fichier de configuration face-config.xml.
La déclaration de ce système de navigation ressemble à celle utilisée dans le framework Struts.
Le système de navigation peut être statique ou dynamique. Dans ce dernier cas, des traitements particuliers doivent être mis en place pour déterminer la cible de la navigation.
Exemple : |
...
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/accueil.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
La tag <navigation-rule> permet de préciser des règles de navigation.
La tag <from-view-id> permet de préciser la page concernée. Ce tag n'est pas obligatoire : sans sa présence, il est possible de définir une règle de navigation applicable à toutes les pages JSF de l'application.
Exemple : |
<navigation-rule>
<navigation-case>
<from-outcome>logout</from-outcome>
<to-view-id>/logout.jsp</to-view-id>
</navigation-case>
</navigation-rule>
Il est aussi possible de désigner un ensemble de pages dans le tag <from-view-id> en utilisant le caractère * dans la valeur du tag. Ce caractère * ne peut être utilisé qu'une seule fois dans la valeur du tag et il doit être en dernière position.
Exemple : |
<from-view-id>/admin/*</from-view-id>
Le tag <navigation-case> permet de définir les différents cas.
La valeur du tag <from-outcome> doit correspondre au nom d'une action.
Le tag <to-view-id> permet de préciser la page qui sera affichée. L'URL fournie comme valeur doit commencer par un slash et doit préciser une page possédant une extension brute (ne surtout pas mettre une URL utilisée par la servlet faisant office de contrôleur).
Le tag <redirect/> inséré juste après le tag <to-view-id> permet au navigateur de l'utilisateur d'effectuer la redirection vers la page indiquée.
La gestion de la navigation est assurée par une instance de la classe NavigationHandler, gérée au niveau de l'application. Ce gestionnaire utilise la valeur d'un attribut action d'un composant pour déterminer la page suivante et faire la redirection vers la page adéquate en fonction des informations fournies dans le fichier de configuration.
La valeur de l'attribut action peut être statique : dans ce cas la valeur est en dur dans le code de la vue
Exemple : |
<h:commandButton action="login"/>
La valeur de l'attribut action peut être dynamique : dans ce cas la valeur est déterminée par l'appel d'une méthode d'un bean
Exemple : |
<h:commandButton action="#{login.verifierMotDePasse}"/>
Dans ce cas, la méthode appelée ne doit pas avoir de paramètre et doit retourner une chaîne de caractères définie dans le fichier de configuration.
Lors des traitements par le NavigationHandler, si aucune action ne trouve de correspondance dans le fichier de configuration pour la page alors la page est simplement réaffichée.
Le modèle de gestion des événements de JSF est similaire à celui utilisé dans les JavaBeans : il repose sur les Listener et les Event pour traiter les événements générés dans les composants graphiques suite aux actions de l'utilisateur.
Un objet de type Event encapsule le composant à l'origine de l'événement et des données relatives à cet événement.
Pour être notifié d'un événement particulier, il est nécessaire d'enregistrer un objet qui implémente l'interface Listener auprès du composant concerné.
Lors de certaines actions de l'utilisateur, un événement est émis.
L'implémentation JSF propose deux types d'événements :
JSF propose de transposer le modèle de gestion des événements des interfaces graphiques des applications standalone aux applications de type web utilisant JSF.
La gestion des événements repose donc sur deux types d'objets
Comme pour les interfaces graphiques des applications standalone, la classe de type Listener doit s'enregistrer auprès du composant concerné. Lorsque celui-ci émet un événement suite à une action de l'utilisateur, il appelle le Listener enregistré en lui fournissant en paramètre un objet de type Event.
Exemple : |
<h:selectOneMenu ... valueChangeListener="#{choixLangue.langueChangement}">
...
</h:selectOneMenu>
JSF supporte trois types d'événements :
Les traitements des listeners peuvent affecter la suite du cycle de vie de plusieurs manières :
Il y a deux façons de préciser un listener de type valueChangeListener sur un composant :
L'attribut valueChangeListener permet de préciser, par une expression, la méthode exécutée durant les traitements du cyle de vie de la requête. Pour que ces traitements puissent être déclenchés, il faut soumettre la page.
Exemple : |
<h:selectOneMenu value="#{choixLangue.langue}" onchange="submit()"
valueChangeListener="#{choixLangue.langueChangement}">
<f:selectItems value="#{choixLangue.langues}"/>
</h:selectOneMenu>
La méthode ne renvoie aucune valeur et attend en paramètre un objet de type ValueChangeEvent.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.Locale;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
public class ChoixLangue {
private static final String LANGUE_FR = "Français";
private static final String LANGUE_EN = "Anglais";
private String langue = LANGUE_FR;
private SelectItem[] langueItems = {
new SelectItem(LANGUE_FR, "Français"),
new SelectItem(LANGUE_EN, "Anglais") };
public SelectItem[] getLangues() {
return langueItems;
}
public String getLangue() {
return langue;
}
public void setLangue(String langue) {
this.langue = langue;
}
public void langueChangement(ValueChangeEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
System.out.println("Changement de la langue : "+event.getNewValue());
if (LANGUE_FR.equals((String) event.getNewValue()))
context.getViewRoot().setLocale(Locale.FRENCH);
else
context.getViewRoot().setLocale(Locale.ENGLISH);
}
}
}
La classe ValueChangeEvent possède plusieurs méthodes utiles :
Méthode | Rôle |
UIComponent getComponent() | renvoie le composant qui a généré l'événement |
Object getNewValue() | renvoie la nouvelle valeur (convertie et validée) |
Object getOldValue() | renvoie la valeur précédente |
Le tag valueChangeListener permet aussi de préciser un listener. Son attribut type permet de préciser une classe implémentant l'interface ValueChangeListener.
Exemple : |
<h:selectOneMenu value="#{choixLangue.langue}" onchange="submit()">
<f:valueChangeListener type="fr.jmdoudoux.dej.jsf.ChoixLangueListener"/>
<f:selectItems value="#{choixLangue.langues}"/>
</h:selectOneMenu>
Une telle classe doit définir une méthode processValueChange() qui va contenir les traitements exécutés en réponse à l'événement.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.Locale;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
public class ChoixLangueListener implements ValueChangeListener {
private static final String LANGUE_FR = "Français";
private static final String LANGUE_EN = "Anglais";
public void processValueChange(ValueChangeEvent event)
throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
System.out.println("Changement de la langue : " + event.getNewValue());
if (LANGUE_FR.equals((String) event.getNewValue()))
context.getViewRoot().setLocale(Locale.FRENCH);
else
context.getViewRoot().setLocale(Locale.ENGLISH);
}
}
}
Les actions sont des clics sur des boutons ou des liens. Le clic sur un composant de type commandLink ou commandButton déclenche automatiquement la soumission de la page.
Il y a deux façons de préciser un listener de type actionListener sur un composant :
L'attribut actionListener permet de préciser, par une expression, la méthode exécutée durant les traitements du cyle de vie de la requête.
Exemple : |
<table align="center" width="50%">
<tr>
<td width="50%"><h:commandButton image="images/bouton_valider.gif"
actionListener="#{saisieDonnees.traiterAction}"
id="Valider" />
</td>
<td><h:commandButton image="images/bouton_annuler.gif"
actionListener="#{saisieDonnees.traiterAction}"
id="Annuler"/>
</td>
</tr>
</table>
Cette méthode attend en paramètre un objet de type ActionEvent.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
public class SaisieDonnees {
public void traiterAction(ActionEvent e) {
FacesContext context = FacesContext.getCurrentInstance();
String clientId = e.getComponent().getClientId(context);
System.out.println("traiterAction : clientId=" + clientId);
}
}
Le tag valueChangeListener permet aussi de préciser un listener. Son attribut type permet de préciser une classe implémentant l'interface ValueChangeListener.
Exemple : |
<table align="center" width="50%">
<tr>
<td width="50%"><h:commandButton image="images/bouton_valider.gif" id="Valider" >
<f:actionListener type="fr.jmdoudoux.dej.jsf.SaisieDonneesListener"/>
</h:commandButton>
</td>
<td><h:commandButton image="images/bouton_annuler.gif" id="Annuler">
<f:actionListener type="fr.jmdoudoux.dej.jsf.SaisieDonneesListener"/>
</h:commandButton>
</td>
</tr>
</table>
Une telle classe doit définir la méthode processAction() définie dans l'interface.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
public class SaisieDonneesListener implements ActionListener {
public void processAction(ActionEvent e) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
String clientId = e.getComponent().getClientId(context);
System.out.println("processAction : clientId=" + clientId);
}
}
L'attribut immediate permet de demander les traitements immédiats des listeners.
Par exemple, sur une page un composant possède un attribut required et un second possède un listener. Les traitements du second doivent pouvoir être réalisés sans que le premier composant n'affiche un message d'erreur lié à sa validation.
Le cycle de traitement de la requête est modifié lorsque l'attribut immediate est positionné dans un composant. Dans ce cas, les données du composant sont converties et validées si nécessaire puis les traitements du listener sont exécutés à la place de l'étape « Process validations » (juste après l'étape Apply Request Value).
Exemple : |
<h:selectOneMenu value="#{choixLangue.langue}" onchange="submit()" immediate="true">
<f:valueChangeListener type="fr.jmdoudoux.dej.jsf.ChoixLangueListener"/>
<f:selectItems value="#{choixLangue.langues}"/>
</h:selectOneMenu>
Par défaut, ceci modifie l'ordre d'exécution des traitements du cycle de vie mais n'empêche pour les traitements prévus de s'exécuter. Pour les inhiber, il est nécessaire de demande au framework JSF d'interrompre les traitements du cycle de vie en utilisant la méthode renderResponse() du FaceCcontext.
Exemple : |
public void langueChangement(ValueChangeEvent event) {
FacesContext context = FacesContext.getCurrentInstance();
System.out.println("Changement de la langue : " + event.getNewValue());
if (LANGUE_FR.equals((String) event.getNewValue())) {
context.getViewRoot().setLocale(Locale.FRENCH);
} else {
context.getViewRoot().setLocale(Locale.ENGLISH);
}
context.renderResponse();
}
Le mode de fonctionnement est le même avec les actionListener hormis le fait que l'appel à la méthode renderResponse() est inutile puisqu'il est automatiquement fait par le framework.
Le framework émet des événements avant et après chaque étape du cycle de vie des requêtes. Ils sont traités par des phaseListeners.
L'enregistrement d'un phaseListener se fait dans le fichier de configuration dans un tag fils <phase-listener> fils du tag <lifecycle> qui doit contenir le nom pleinement qualifié d'une classe.
Exemple : |
<faces-config>
...
<lifecycle>
<phase-listener>fr.jmdoudoux.dej.jsf.PhasesEcouteur</phase-listener>
</lifecycle>
</faces-config>
La classe précisée doit implémenter l'interface javax.faces.event.PhaseListener qui définit trois méthodes :
La classe PhaseId définit des constantes permettant d'identifier chacune des phases : PhaseId.RESTORE_VIEW, PhaseId.APPLY_REQUEST_VALUES, PhaseId.PROCESS_VALIDATIONS, PhaseId.UPDATE_MODEL_VALUES, PhaseId.INVOKE_APPLICATION et PhaseId.RENDER_RESPONSE
Elle définit aussi la constante PhaseId.ANY_PHASE qui permet de demander l'application du listener à toutes les phases. Cela peut être très utile lors du débogage.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
public class PhasesEcouteur implements PhaseListener {
public void afterPhase(PhaseEvent pe) {
System.out.println("Apres " + pe.getPhaseId());
}
public void beforePhase(PhaseEvent pe) {
System.out.println("Avant " + pe.getPhaseId());
}
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}
Lors de l'appel de la première page de l'application, les informations suivantes sont affichées dans la sortie standard :
Avant RESTORE_VIEW 1
Apres RESTORE_VIEW 1
Avant RENDER_RESPONSE 6
Apres RENDER_RESPONSE 6
Lors d'une soumission de cette page avec une erreur de validation des données, les informations suivantes sont affichées dans la sortie standard :
Avant RESTORE_VIEW 1
Apres RESTORE_VIEW 1
Avant APPLY_REQUEST_VALUES 2
Apres APPLY_REQUEST_VALUES 2
Avant PROCESS_VALIDATIONS 3
Apres PROCESS_VALIDATIONS 3
Avant RENDER_RESPONSE 6
Apres RENDER_RESPONSE 6
Lors d'une soumission de cette page sans erreur de validation des données, les informations suivantes sont affichées dans la sortie standard :
Avant RESTORE_VIEW 1
Apres RESTORE_VIEW 1
Avant APPLY_REQUEST_VALUES 2
Apres APPLY_REQUEST_VALUES 2
Avant PROCESS_VALIDATIONS 3
Apres PROCESS_VALIDATIONS 3
Avant UPDATE_MODEL_VALUES 4
Apres UPDATE_MODEL_VALUES 4
Avant INVOKE_APPLICATION 5
Apres INVOKE_APPLICATION 5
Avant RENDER_RESPONSE 6
Apres RENDER_RESPONSE 6
Une application utilisant JSF s'exécute dans un serveur d'applications contenant un conteneur web implémentant les spécifications servlet 1.3 et JSP 1.2 minimum. Une telle application doit être packagée dans un fichier .war.
La compilation des différentes classes de l'application nécessite l'ajout dans le classpath de la bibliothèque servlet.
Elle nécessite aussi l'ajout dans le classpath de la bibliothèque jsf-api.jar de la ou des bibliothèques requises par l'implémentation JSF utilisée.
Ces bibliothèques doivent aussi être disponibles pour le conteneur web qui va exécuter l'application. Le plus simple est de mettre ces fichiers dans le répertoire WEB-INF/lib.
Cette section va développer une petite application constituée de deux pages. La première va demander le nom de l'utilisateur et la seconde afficher un message de bienvenue.
Il faut créer un répertoire, par exemple nommé Test_JSF et créer à l'intérieur la structure de l'application qui correspond à la structure de toute application Web selon les spécifications J2EE, notamment le répertoire WEB-INF avec ses sous-répertoires lib et classes.
Il faut ensuite copier les fichiers nécessaires à une utilisation de JSF dans l'application web.
Il suffit de copier les fichiers *.jar du répertoire lib de l'implémentation de référence vers le répertoire WEB-INF/lib du projet.
Il faut créer un fichier à la racine du projet et le nommer index.htm
Exemple : |
<html>
<head>
<meta http-equiv="Refresh" content= "0; URL=login.faces"/>
<title>Demarrage de l'application</title>
</head>
<body>
<p>Démarrage de l'application ...</p>
</body>
</html>
Il faut créer un fichier à la racine du projet et le nommer login.jsp :
Exemple : |
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<head>
<title>Application de tests avec JSF</title>
</head>
<body>
<h:form>
<h3>Identification</h3>
<table>
<tr>
<td>Nom : </td>
<td><h:inputText value="#{login.nom}"/></td>
</tr>
<tr>
<td>Mot de passe :</td>
<td><h:inputSecret value="#{login.mdp}"/></td>
</tr>
<tr>
<td colspan="2"><h:commandButton value="Login" action="login"/></td>
</tr>
</table>
</h:form>
</body>
</f:view>
</html>
Il faut créer une nouvelle classe nommée fr.jmdoudoux.dej.jsf.LoginBean et la compiler dans le répertoire WEB-INF/classes.
Exemple : |
package fr.jmdoudoux.dej.jsf;
public class LoginBean {
private String nom;
private String mdp;
public String getMdp() {
return mdp;
}
public String getNom() {
return nom;
}
public void setMdp(String string) {
mdp = string;
}
public void setNom(String string) {
nom = string;
}
}
Il faut créer un fichier à la racine du projet et le nommer accueil.jsp : cette page contiendra la page d'accueil de l'application.
Il faut créer un fichier dans le répertoire /WEB-INF et le nommer faces-config.xml :
Exemple : |
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/accueil.jsp</to-view-id>
</navigation-case>
</navigation-rule>
<managed-bean>
<managed-bean-name>login</managed-bean-name>
<managed-bean-class>fr.jmdoudoux.dej.jsf.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>
Il faut créer un fichier dans le répertoire /WEB-INF et le nommer web.xml :
Exemple : |
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<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>index.htm</welcome-file>
</welcome-file-list>
</web-app>
Il suffit alors de démarrer Tomcat, puis d'ouvrir un navigateur et taper l'URL http://localhost:8089/test_JSF/ (en remplaçant le port 8089 par celui défini dans Tomcat).
Une fois l'application démarrée, la page de login s'affiche
Il faut saisir un nom par exemple test et cliquer sur le bouton « Login ».
Cette exemple ne met en aucune façon en valeur la puissance de JSF mais permet simplement de mettre en place les éléments minimum pour une application l'utilisant.
JSF propose des fonctionnalités qui facilitent l'internationalisation d'une application.
Il faut définir un fichier au format properties qui va contenir la définition des chaînes de caractères. Un tel fichier possède les caractéristiques suivantes :
Exemple : le fichier msg.properties |
login_titre=Application de tests avec JSF
login_identification=Identification
login_nom=Nom
login_mdp=Mot de passe
login_Login=Valider
Ce fichier correspond à la langue par défaut. Il est possible de définir d'autres fichiers pour d'autres langues. Ces fichiers doivent avoir le même nom suivi d'un underscore et du code langue défini par le standard ISO 639 avec toujours l'extension .properties.
Exemple : |
msg.properties
msg_en.properties
msg_de.properties
Il faut bien sûr remplacer les valeurs de chaque chaîne par leurs traductions correspondantes.
Exemple : |
login_titre=Tests of JSF
login_identification=Login
login_nom=Name
login_mdp=Password
login_Login=Login
Les langues disponibles doivent être précisées dans le fichier de configuration.
Exemple : |
<faces-config>
...
<application>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>en</supported-locale>
</locale-config>
</application>
...
</faces-config>
Pour utiliser l'internationalisation dans les vues, il faut utiliser le tag <f:loadBundle> pour charger le fichier .properties nécessaire. Deux attributs de ce tags sont requis :
Il ne reste plus qu'à utiliser la variable définie en utilisant la notation avec un point pour la clé de la chaîne dont on souhaite utiliser la valeur.
Exemple : |
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<f:loadBundle basename="fr.jmdoudoux.dej.jsf.msg" var="msg"/>
<head>
<title><h:outputText value="#{msg.login_titre}"/></title>
</head>
<body>
<h:form>
<h3><h:outputText value="#{msg.login_identification}"/></h3>
<table>
<tr>
<td><h:outputText value="#{msg.login_nom}"/> : </td>
<td><h:inputText value="#{login.nom}"/></td>
</tr>
<tr>
<td><h:outputText value="#{msg.login_mdp}"/> :</td>
<td><h:inputSecret value="#{login.mdp}"/></td>
</tr>
<tr>
<td colspan="2"><h:commandButton value="#{msg.login_Login}" action="login"/></td>
</tr>
</table>
</h:form>
</body>
</f:view>
</html>
La langue à utiliser est déterminée automatiquement par JSF en fonction des informations contenues dans la propriété Accept-Language de l'en-tête de la requête et du fichier de configuration.
La langue peut aussi être forcée dans l'objet de type view en précisant le code langue dans l'attribut locale.
Exemple : |
...
<f:view locale="en">
...
Elle peut aussi être déterminée dans le code des traitements. L'exemple suivant va permettre à l'utilisateur de sélectionner la langue utilisée entre Français et Anglais grâce à deux petites icônes cliquables.
Exemple : |
<html>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<f:view>
<f:loadBundle basename="fr.jmdoudoux.dej.jsf.Messages" var="msg"/>
<head>
<title>Application de tests avec JSF</title>
</head>
<body>
<h:form>
<table>
<tr>
<td>
<h:commandLink action="#{langueApp.activerFR}" immediate="true">
<h:graphicImage value="images/francais.jpg" style="border: 0px"/>
</h:commandLink>
</td>
<td>
<h:commandLink action="#{langueApp.activerEN}" immediate="true">
<h:graphicImage value="images/anglais.jpg" style="border: 0px"/>
</h:commandLink>
</td>
<td width="100%"> </td>
</tr>
</table>
<h3><h:outputText value="#{msg.login_titre}" /></h3>
<p> </p>
<h:panelGrid columns="2">
<h:outputText value="#{msg.login_nom}" />
<h:panelGroup>
<h:inputText value="#{login.nom}" id="nom" required="true"
binding="#{login.inputTextNom}"/>
<h:message for="nom"/>
</h:panelGroup>
<h:outputText value="#{msg.login_mdp}" />
<h:inputSecret value="#{login.mdp}"/>
<h:commandButton value="#{msg.login_valider}" action="login"/>
</h:panelGrid>
</h:form>
</body>
</f:view>
</html>
Ce code n'a rien de particulier si ce n'est l'utilisation de l'attribut immediate sur les liens sur le choix de la langue pour empêcher la validation des données lors d'un changement de la langue d'affichage.
Ce sont les deux méthodes du bean qui se chargent de modifier la Locale par défaut du contexte de l'application.
Exemple : |
package fr.jmdoudoux.dej.jsf;
import java.util.Locale;
import javax.faces.context.FacesContext;
public class LangueApp {
public String activerFR() {
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(Locale.FRENCH);
return null;
}
public String activerEN() {
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(Locale.ENGLISH);
return null;
}
}
Lors de l'exécution, la page s'affiche en français par défaut.
Lors d'un clic sur la petite icône indiquant la langue anglaise, la page est réaffichée en anglais.
Malgré ses nombreux points forts, JSF présente aussi quelques points faibles :
JSF est une technologie récente qui nécessite l'écriture de beaucoup de code. Bien que prévu pour être utilisé avec des utilitaires facilitant la rédaction de la majeure partie de ce code, à l'heure actuelle, seuls quelques outils supportent JSF.
L'implémentation standard ne propose que des composants simples dont la plupart ont une correspondance directe en HTML. Hormis le composant dataTable aucun composant évolué n'est proposé en standard dans la version 1.0. Il est donc nécessaire de développer ses propres composants ou d'acquérir les composants nécessaires auprès de tiers.
L'exécution d'une application JSF est assez gourmande en ressource notamment mémoire à cause du mode de fonctionnement du cycle de traitement d'une page. Ce cycle de vie inclut la création en mémoire d'une arborescence des composants utilisés lors des différentes étapes des traitements.
Dans l'implémentation de référence le rendu des composants est uniquement possible en HTML alors que JSF intègre un système de rendu (Renderer) découplé des traitements des composants. Pour un rendu différent de HTML, il est nécessaire de développer ses propres Renderers ou d'acquérir un système de rendu auprès de tiers.
|
La suite de ce chapitre sera développée dans une version future de ce document
|
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |