Java Server Faces (JSF) est défini par la JSR 314. Il s'agit d'un framework permettant de créer des applications
Web complètes.
Créer un projet avec JSF
L'implémentation de JSF intégrée dans TomEE est celle de la communauté Apache : MyFaces.
MyFaces est une Servlet qu'il faut déclarer dans le fichier web.xml de son application :
JSF supporte une configuration de développement pour permettre un rechargement presque à chaud
lorsque des modifications sont apportées à l'application. Pour l'activer, il faut rajouter
au début du fichier web.xml un paramètre d'application :
Lorsque vous déploierez votre application dans TomEE, vous verrez dans la log
de démarrage un message d'avertissement confirmant l'activation du mode développement :
Comme tous les services Java EE, JSF dispose d'un fichier de déploiement au format XML.
Ce fichier de déploiement s'appelle faces-config.xml et doit être situé dans
le répertoire WEB-INF. Le contenu minimal de ce fichier est :
Pour l'utilisation de JSF en vue d'un déploiement dans TomEE, nous allons également avoir besoin d'un service Java EE : CDI
(Contexts and Dependency Injection). Nous reviendrons plus tard sur l'utilité de ce service. CDI n'est pas
activé par défaut pour une application Web. Pour l'activer, il suffit d'ajouter le fichier de déploiement
de CDI pour l'application. Ce fichier doit s'appeler beans.xml et être situé dans le
répertoire WEB-INF. Le contenu minimal de ce fichier est :
La vue : facelets
JSF n'utilise pas les JSP, JSF dispose de son propre langage de déclaration de vue appelé facelet.
Du point de vue du développeur, nous allons voir qu'il n'y a pas une très grande différence entre une JSP et une
facelet. Par contre, il s'agit de deux technologies différentes : les balises supportées ne sont pas les
mêmes et une facelet n'est pas transformée en Servlet.
Une facelet est un document XHTML 1.0 qui doit se conformer à la DTD XHTML-1.0-Transitional.
Avec le succés de HTML5, des adaptations ont été faites dans JSF (Java EE 7) pour permettre de développer des facelets HTML5. Néanmoins,
une facelet doit être un document XML bien formé.
Une facelet est un document XML que le moteur JSF va analyser pour rechercher des balises spécifiques JSF.
L'utilisation des balises de facelets se fait grâce aux espaces de nom XML (XML namespaces).
Il existe six bibliothèques standards de balises JSF. Chacune dispose de son propre espace de nom XML.
Préfixe
XML namespace
Description
Exemples de balise
h
http://java.sun.com/jsf/html
Contient les balises pour le rendu HTML des éléments pris en charge par JSF
h:head h:body h:form h:inputText
f
http://java.sun.com/jsf/core
Contient les balises qui ne génèrent pas de rendu HTML mais assurent l'intéraction avec le serveur et le formattage de données
f:actionListener f:ajax
c
http://java.sun.com/jsp/jstl/core
Contient les balises de la bibliothèque JSTL core (Cf cours sur les JSP).
Attention cette bibliothèque a été amputée notamment de la balise c:out par rapport à la version JSP.
En JSF, on utilise h:outputText à la place.
c:if c:forEach
fn
http://java.sun.com/jsp/jstl/functions
Contient les fonctions de la bibliothèque JSTL functions (Cf cours sur les JSP).
Cette bibliothèque ne contient pas de balise.
fn:contains fn:join
ui
http://java.sun.com/jsf/facelets
Contient les balises permettant des compositions de vues. Il est possible de définir un layout
pour l'ensemble de l'application et d'appliquer automatiquement ce layout à chaque facelet.
ui:component ui:composition
cc
http://java.sun.com/jsf/composite
Permet de définir de nouveaux composants graphiques.
cc:interface cc:implementation
La bibliothèque JSF HTML (http://java.sun.com/jsf/html) contient notamment les balises
h:head,
h:body.
Ces balises permettent d'indiquer au moteur JSF les parties du document HTML qui correspondent aux en-entêtes et au corps.
JSF utilise ces informations pour éventuellement enrichir la page XHTML finale avec des balises supplémentaires.
On retrouve beaucoup de similitudes entre facelets et JSP. Par exemple, voici une facelet
affichant les paramètres de la requête HTTP :
Il existe quasiment les mêmes objets implicites
accessibles avec l'expression language (EL) que pour les JSP. Comme une JSP, une facelet peut accéder
aux attributs des différentes portées (page, request, session, application). Cependant pour créer ces
attributs, nous n'allons plus utiliser l'API servlet comme vu précédemment, nous allons utiliser
le service CDI.
Une introduction à CDI
Contexts and Dependency Injection (CDI) est un service Java EE permettant
de déclarer des objets Java qui seront automatiquement créés par le serveur et positionnés comme
attributs dans la portée désirée. Ensuite ces objets sont accessibles depuis une JSP ou une facelets
ou encore peuvent être injectés dans un composant Java EE ou un autre objet géré par CDI.
Déclarer un objet avec CDI
Nous verrons dans l'exemple ci-dessous une déclaration par annotations :
L'annotation @Named
suffit à indiquer que cette classe peut être gérée par CDI.
L'annotation @RequestScoped
indique que l'instance de l'objet sera un attribut de portée requête.
Sur le même principe, il existe les annotations
@SessionScoped
et @ApplicationScoped.
Par défaut, le nom de l'instance sera le même que le nom de la classe commençant par une minuscule.
Ainsi, une fois cette classe ajoutée dans le projet, il est possible de l'utiliser dans une facelet :
S'il n'existe pas d'attribut personneControleur, celui-ci sera automatiquement créé avec une portée requête.
Il est également possible de spécifier soi-même le nom du bean dans l'annotation @Named :
@Named("monControleurDePersonne")
Le fichier de déploiement beans.xml
Comme la plupart des services Java EE, CDI dispose d'un fichier de déploiement appelé beans.xml.
Ce fichier sert à déclarer des fonctionnalités avancées pour CDI mais il doit également être présent dans
l'arborescence de l'application pour indiquer à TomEE d'activer le service CDI pour cette application.
Pour une application Web, le fichier beans.xml doit se trouver dans le répertoire WEB-INF. Sa structure minimale
est :
Le modèle
Dans une application JSF, n'importe quelle instance d'objet Java peut jouer le rôle du modèle. Le
modèle peut être un objet géré par CDI ou rendu disponible par un objet géré par CDI (ce dernier jouant alors le rôle de contrôleur).
Par exemple, une instance de la classe Personne peut être utilisée comme modèle
dans un formulaire d'une facelet en y accédant via le bean CDI personneControleur vu précédemment :
Notez que depuis le début de ce chapitre, les expressions en EL (expression language) utilisées dans les facelets
sont délimitées par #{ }. Comme pour les JSP, JSF supporte l'écriture d'une EL
sous la forme ${ }. Cependant, l'utilisation du caractère #
indique que l'on souhaite activer le value binding. Cette fonctionnalité
indique au moteur JSF, que le contenu du bean personneControleur.personne devra également être mis à jour
avec les données envoyées par le client. Concrètement, il est plus simple d'utiliser
systématiquement avec JSF la notation #{ }.
Le contrôleur
JSF est basé sur l'API servlet mais il permet aux développeurs d'application Web de s'en affranchir.
Ainsi, avec JSF, un contrôleur est simplement une classe Java gérée par CDI qui expose des méthodes
qui seront appelées par JSF lors de la réception des requêtes du client.
Dans la terminologie JSF, on parle de backing beans pour désigner les objets Java avec lesquels la facelet iteragit.
La génération d'action vers le contrôleur se fait lorsque le client envoie des données vers le serveur.
En HTML, cela se fait par la soumission de formulaire. Avec JSF, la soumission de formulaire peut se faire avec la balise h:commandLink ou
la balise h:commandButton. Ces deux balise JSF disposent de l'attribut action
qui permet d'écrire une EL définissant l'appel à une fonction d'un backing bean (une instance gérée par CDI).
Il est possible de préciser dans l'EL les paramètres qui seront passés à la méthode côté serveur.
Dans la facelet ci-dessus, l'action déclenchée par le bouton "chercher" est :
#{personneControleur.chercher()}
Cela signifie que JSF va chercher un bean CDI portant le nom "personneControleur"
et il va invoquer la méthode chercher. Au préalable, les attributs nom et age
de la propriété personneControleur.personne auront
été mis à jour avec les valeurs envoyées dans la requête.
Ainsi un contrôleur valide pourrait être :
La navigation
Une fonctionnalité importante des frameworks Web est la gestion de la navigation.
Après avoir traité une requête, vers quelle vue, un contrôleur doit-il déléguer le traitement
pour construire la représentation finale ?
Dans JSF, les vues sont les fichiers XHTML (les facelets). Les identifiants des facelets correspondent
simplement au nom du fichier sans l'extension .xhtml. Une méthode de contrôleur indique la vue résultat
en retournant son identifiant. Si la méthode de contrôleur ne retourne aucune valeur (void) ou retourne
null, la vue résultat est la vue courante.
Si nous reprenons notre exemple de contrôleur, nous pouvons indiquer la vue résultat en modifiant
la méthode chercher pour qu'elle retourne une chaîne de caractères.
Pour la navigation par liens, il est possible d'utiliser les balises h:link et h:button
dans les facelets. Ces balises disposent de l'attribut outcome. Cet attribut donne l'identifiant
de la facelet cible. Bien sûr, la valeur de l'attribut outcome peut être le résultat d'une EL.
La validation de formulaire
La validation des données de formulaire est une autre fonctionnalité importante des frameworks Web.
La biblothèque de balises core
de JSF fournit, entre autres, les balises f:validateDoubleRange, f:validateLength, f:validateLongRange,
f:validateRegex et f:validateRequired.
Utilisées comme balises filles des entrées de formulaire, elles permettent d'ajouter des règles de validité
pour les données de formulaire. JSF validera automatiquement les données soumises par l'utilisateur avant
de transférer le traitement au contrôleur.
Si la validation échoue, JSF retourne la même vue au client sans solliciter le contrôleur.
La vue dispose dans son contexte des messages d'erreur de validation. La balise h:message
permet d'indiquer où les erreurs d'une entrée de formulaire seront affichées dans la réponse.
Il est également possible de fournir sa propre implémentation d'un validateur. Pour cela, il suffit de créer une classe qui implémente l'interface
javax.faces.validator.Validator.
Cette interface ne contient la déclaration que d'une seule méthode :
La validation échoue si un appel à cette méthode lance une ValidatorException.
Le premier paramètre représente le contexte d'exécution JSF, le deuxième paramètre représente le composant graphique pour lequel la validation a été demandée.
Par exemple, pour un champ de formulaire de type input, ce composant sera une instance de
UIInput
qui hérite de UIComponent.
Enfin le troisième paramètre représente la valeur qui doit être validée. Selon le type de composant, cette valeur peut être de type String, Boolean...
La classe du validateur doit également porter l'annotation @FacesValidator indiquant le
nom du validateur qui sera utilisé pour le référencer dans les facelets.
L'exemple ci-dessous montre un exemple de validateur permettant de s'assurer qu'une case à cocher (checkbox) a bien été cochée par l'utilisateur.
La validation avec Bean Validation
Le serveur d'application fournit un autre service nommé Bean Validation (JSR303).
Bean Validation permet d'exprimer les contraintes de validité d'un objet avec des annotations. JSF est capable d'interagir
avec Bean Validation pour la validation de formulaire. Ainsi, plutôt que de déclarer la validation dans une facelet comme
dans la section précédente, il est possible d'ajouter des annotations directement sur le bean Personne
Bean Validation est une bonne alternative aux balises JSF de validation si un bean doit être
réutilisé comme modèle dans des facelets différentes.
Les requêtes Ajax
Une requête Ajax est une requête asynchrone qui est exécutée par le navigateur. Lorsque le serveur renvoie
la réponse au navigateur, ce dernier ne modifie pas la page affichée mais rend accessible la réponse en JavaScript.
JSF supporte Ajax sans que le développeur Web n'ait à implémenter du code JavaScript.
JSF injecte lui-même le code JavaScript nécessaire au moment du rendu de la facelet.
Pour activer Ajax, il suffit, par exemple, de spécifier la balise f:ajax
comme balise fille d'un
h:commandButton :
La déclaration ci-dessus suffit à générer automatiquement le code javascript dans la page XHTML pour que,
lorsque l'utilisateur clique sur le bouton, une requête Ajax soit soumise au serveur. La balise f:ajax
a deux attributs importants :
execute : liste les composants qui sont pris en compte par la requête Ajax. Il est possible
de lister les id des élements d'un formulaire séparés par un espace. On peut aussi utiliser un des mots-clés
suivants : @form (tous les éléments du formulaire courant), @this (uniquement le composant
qui contient la balise f:ajax), @all (tous les composants graphiques JSF de la page), @none (aucune composant
n'est associé à la requête Ajax).
render : spécifie l'ID ou la liste des ID des composants graphiques JSF dans la facelet qui doivent être mis à jour lors de la réception
de la réponse Ajax. Comme pour l'attribut précédent, il est possible d'utiliser les mots-clés @this, @form, @all et @none
Pour le support d'Ajax, l'implémentation du contrôleur se fait souvent avec deux méthodes :
une méthode pour permettre au contrôleur de recevoir les données de la facelet et une méthode pour permettre à la facelet d'obtenir, en retour, les résultats
qui permettront de mettre à jour une partie de la page.
Un exemple simple mais complet serait :
... et bien plus
Ce chapitre n'avait pour ambition que de présenter les fonctionnalités les plus essentielles de JSF.
Avec JSF, vous avez aussi la possibilité de créer un layout pour l'ensemble de l'application ou de créer
en facelet vos propres composants graphiques réutilisables.
Vous pouvez également enrichir votre application avec des composants graphiques plus complexes.
On pourra par exemple incorporer des bibliothèques tierces comme la très impressionante
PrimeFaces.