Ce TP montre comment utiliser des templates.
Les templates sont des "pages types" dans lesquelles ont peut définir des zones que l'on pourra remplacer dans les "clients" des templates. Ils définissent une charte graphique pour l'application : toutes les pages auront un aspect similaire, ce qui améliore souvent l'ergonomie.
En outre, dans un modèle on peut définir des paramètres (en gros, des variables qui pourront être remplacées par des valeurs, spécifiées dans les pages qui utilisent ces modèles). Et enfin, un modèle peut lui même réutiliser un autre modèle, l'enrichir etc.
Le plus souvent l'utilisation d'un template est très simple. On verra comment faire dans le premier exercice.
Pour ceux qui veulent aller plus loin l'exercice "Plus loin avec les templates..." montre comment créer des templates de templates. Cette possibilité est moins souvent utilisée mais elles peut rendre des services en factorisant du code. Cet exercice est optionnel. Vous pourrez l'étudier après la fin du cours pour voir s'il peut vous être utile pour écrire vos projets.
Ressources :
Support JSF distribué pour ce cours.
Vous allez travailler sur le même projet que celui du TP sur PRG. Vous avez donc déjà les pages formulaire_1.xhtml, affichage_1.xhtml ainsi que les autres pages numérotées 2, 3 et 4, ainsi que 2 backing beans.
Rappel : n'oubliez pas les commits, et les pushs sur GitHub.
Vous allez créer un template pour faciliter le test des différents exercices du TP sur PRG : un menu inclus dans le template permettra de choisir facilement l'exercice que l'on veut tester.
Dans cet exercice d'introduction il n'y a qu'un seul template qui ne comporte pas de paramètres.
Ce template comprend 3 parties :
Voici ce que l'on désire obtenir (c'est la page d'accueil qui est affichée ; elle contient une description des exercices) :
Vous auriez pu mettre le template dans la racine des pages Web mais comme le fichier du template ne pourra pas être appelé directement par les utilisateurs de votre application il est préférable de le mettre dans le répertoire WEB-INF. Créez un sous-répertoire "templates" dans WEB-INF (clic droit sur WEB-INF puis New > Folder...).
Clic droit sur le projet New > Facelets Templates ou bien New > Other > Java Server Faces > Facelets Templates. Donnez un nom au template, "template_defaut" (si vous faites le 2ème exercice vous aurez d'autres templates). Choisissez le template qui correspond aux 3 parties expliquées ci-dessus.
Mettez-le dans le répertoire WEB-INF\templates que vous venez de créer.
Etudiez ce code.
Les parties importantes sont les <ui:insert>
qui correspondent aux parties du template qui seront précisées dans les pages qui utiliseront ce template. Ces <ui:insert>
sont inclus dans des <div>
qui permettront de leur donner facilement un style CSS.
Trouvez le chemin des fichiers CSS dans le <h:head>
. <h:outputStylesheet>
est utilisé (bonne pratique à respecter pour désigner un fichier CSS) et il faut donc chercher les fichiers dans le répertoire resources
. Les emplacements des différentes parties sont définis dans le fichier csslayout.css
. Vous pouvez évidemment modifier ces fichiers si vous vous y connaissez un peu en CSS. Le plus souvent c'est très simple ; par exemple pour élargir la partie gauche, vous pouvez remplacer 150px par 200px dans la définition de #left
, width
, dans le fichier cssLayout.css.
Tout d'abord vous allez corriger une petite imprécision du code généré : dans les <h:ouputStylesheet>
remplacez "./" par "/" ; par exemple
<h:outputStylesheet name="/css/default.css"/>
Les espaces de noms XML d'alias ui et h sont les anciens espaces de noms de JSF ; ils sont encore utilisables, mais vous allez utiliser les nouveaux espaces de noms :
xmlns:h="jakarta.faces.html" xmlns:ui="jakarta.faces.facelets"
Vous allez aussi modifier ce template pour l'adapter à l'application.
Changez le title de la section head ; par exemple en "Exercices sur PRG".
Pour faciliter l'ajout de la balise <f:metadata>
pour certains des exercices ajoutez une nouvelle partie de nom "metadata" qui sera définie dans les clients du template (les pages qui utiliseront le template). Cette balise doit être au niveau supérieur d'une page JSF ou d'un client d'un template et ne doit pas être incluse dans un template (voir https://jakarta.ee/specifications/faces/4.0/vdldoc/). Ajoutez juste avant le <h:head>
(il ne faudrait pas l'inclure dans le <h:body>
) :
<ui:insert name="metadata"/>
La partie gauche sera identique pour toutes les pages qui utilisent le template : il s'agit du menu qui permettra de choisir l'exercice. Vous allez donc le définir tout de suite. Vous pourriez écrire le code du menu directement dans le template (dans la <div id="left">
) mais vous allez plutôt mettre ce code dans un fichier à part que vous allez inclure dans le template (souvent une bonne pratique).
Pour créer le fichier du menu : Clic droit sur le répertoire WEB-INF/templates > New > JSF Page... "menu" est le nom de la page.
Ce fichier va utiliser des <h:link>
. Remarque : utiliser des <a ref=>
ne marcherait pas bien avec des chemins relatifs.
Pour réduire le temps de codage, le menu ne donnera accès qu'aux exercices 1 et 4 (voyez ci-dessous comment éliminer provisoirement les balises pour les autres exercices avec <ui:remove>
).
Transformez la page pour avoir ce code (commencez par remplacer <html>
par <ui:composition>
) :
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html" xmlns:ui="jakarta.faces.facelets"> <ul> <li><h:link outcome="/index" value="Accueil"/></li> <li><h:link outcome="/formulaire__1" value="Exercice 1"/></li> <ui:remove> <li><h:link outcome="/formulaire__2" value="Exercice 2"/></li> <li><h:link outcome="/formulaire__3" value="Exercice 3"/></li> </ui:remove> <li><h:link outcome="/formulaire__4" value="Exercice 4"/></li> </ul> </ui:composition>
La balise <ui:composition>
contient une suite d'éléments qui peuvent être insérés ailleurs dans une page JSF. Si cette balise a l'attribut "template
", elle peut aussi servir à définir les parties variables d'un template dans un client d'un template comme on va le voir bientôt.
ATTENTION, vous allez devoir modifier les pages "formulaire" et "affichage". Pour ne pas écraser les anciennes versions, vous allez écrire des nouvelles pages qui contiennent un double "_" ; par exemple remarquez dans le code ci-dessus "outcome="/formulaire__1.xhtml
" avec 2 "_".
Remarque : prenez l'habitude de donner des chemins absolus pour les outcome car des pages qui utilisent ce template pourraient se trouver dans un sous-répertoire et alors les liens ne fonctionneraient plus.
Modifiez le template pour inclure ce fichier :
<ui:insert name="left"> <ui:include src="menu.xhtml"/> </ui:insert>
Il va falloir créer maintenant les pages qui vont utiliser le template : la page index.xhtml d'accueil de l'application et 2 pages par exercice (pour le formulaire de saisie du nombre et pour l'affichage des nombres qui suivent).
Si la page index.xhtml existe déjà, commencez par la supprimer.
Là encore vous allez utiliser les facilités de NetBeans : clic droit sur le projet > New > Facelets Template Clients... (ou New > Other > Java Server Faces > Facelets Template Client...). Nommez la page cliente "index", désignez le template "template_defaut" et décochez les sections qui ne seront pas utiles pour cette page car elles ne seront pas modifiées par rapport au template : metadata et left (en fait left ne devrait pas apparaître car il n'y a pas de <ui:insert> dans le template pour cette partie).
Etudiez la nouvelle page index.xhtml et modifiez-la :
<p>Cette application présente plusieurs exercices qui illustrent le modèle PRG.</p> <p>Choisissez votre exercice dans le menu de gauche.</p>
Testez en lançant la page index.xhtml (clic droit sur le fichier et "Run File", ou lancez l'application puisqu'il s'agit de la page index.xhtml).
Vous devriez obtenir un résultat correct à part des messages d'erreur car vous n'avez pas encore créé les pages des exercices.
Faites comme avec la page index.xhtml pour créer les 2 pages par exercice (n'oubliez pas de doubler le "_"). Décochez la section left mais attention à ne pas décocher la section "metadata" pour l'exercice 4, et n'oubliez pas le code pour cette section.
Pour chaque page, recopiez dans la section "content" le code des pages du TP PRG.
Voici les étapes pour l'exercice 1 :
<body>
dans formulaire_1.xhtml (celui que vous avez écrit dans le TP PRG). Remettez en forme le contenu par Alt-Maj-F (ou Alt-Shift-F).Faites de même avec affichage__1.xhtml.
Testez l'exercice 1.
Faites les mêmes modifications pour l'exercice 4. N'oubliez pas la section "metadata" du template (et la balise <metadata>
à l'intérieur de cette section). Modifiez aussi le bean (ou bien copiez-le en un autre bean pour que les exercices du TP PRG fonctionnent toujours) pour que la valeur retournée par la méthode afficher() désigne bien affichage__4.xhtml et pas affichage_4.xhtml.
Testez.
Vous allez améliorer un peu le template en ajoutant des paramètres. L'exercice suivant poussera encore plus loin ce paramétrage.
Vous allez donner une valeur par défaut pour le titre de la page dans le template, en utilisant un paramètre :
<div id="top" class="top"> <ui:insert name="top">TP PRG - #{titre}</ui:insert> </div>
Utilisez cette valeur en donnant la valeur du paramètre dans les clients du template. Par exemple, pour formulaire__1.xhtml :
<ui:composition template="./WEB-INF/templates/template_defaut.xhtml">
<ui:param name="titre" value="Exercice 1"/>
Il faut enlever la définition de la section "top" de formulaire__1.xhtml pour que la valeur par défaut soit utilisée.
Testez.
Cet exercice est optionnel. Il s'agit d'une ancienne version de ce qui précède, qui va un peu plus loin avec les templates. Comme les TPs suivants sont longs et importants, il vaut mieux faire le TP suivant et revenir à cet exercice ensuite, après la fin du cours.
On a vu dans l'exercice précédent que les templates sont très simples à utiliser dans JSF. Cet exercice vous semblera peut-être un peu plus difficile. Il utilise des possibilités des templates qui ne sont utilisés que dans les cas d'utilisation les plus complexes.
Prenez le temps de remarquer tout le code qui se répète dans les pages jsf, dans les titres et les h1, et dans la structure des pages. De plus, j'imagine que la plupart d'entre vous doit encore modifier les urls à la main pour essayer une page formulaire_X au démarrage de l'application.
Nous allons maintenant mettre en place plusieurs templates pour alléger tout ce code.
Le template de base est une page avec des parties fixes et des parties qui seront définies dans les pages JSF qui utilisent le template. Ce template définit la structure des pages qui l'utiliseront :
Dans l'image ci-dessus, la page d'accueil est affichée. Elle correspond à la page JSF index.xhtml comme l'indiquera l'URL dans le navigateur. Dans le code de cette page on indique que l'on utilise le template de base et on donne la valeur du paramètre "numEx" et les parties variables (<ui:insert> de nom "complementTitreEtH1" et "content". Pour la page d'accueil, le paramètre "numEx" sera vide, le <ui:insert> de nom "complementTitreEtH1" sera égal au texte "Application>Menu du TP2" et celui de "content" sera le texte des exercices.
Pour factoriser encore davantage du code, le même modèle va être repris par deux autres modèles : un pour les formulaires de saisie des nombres, l'autre par les pages qui affichent les nombres qui suivent le nombre entré par l'utilisateur.
Exemples de résultats, ici la page de saisie du nombre du premier exercice (cette page formulaire_1.xhtml utilise le modèle de formulaire, qui utilise le modèle par défaut) :
et la page d'affichage correspondante (qui utilise un modèle pour l'affichage, qui lui aussi utilise le modèle par défaut) :
etc... vous avez compris le principe, normalement on va pouvoir factoriser au maximum le code et le nombre de lignes dans les pages .xhtml devrait être réduit en conséquence (en plus d'avoir une présentation plus uniforme).
Comment arriver à ce résultat ?
Travail à faire, choses à remarquer :
Copiez-donc ce code pour le template par défaut :
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="jakarta.faces.facelets"
xmlns:h="jakarta.faces.html">
<ui:insert name="metadata"/>
<h:head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<h:outputStylesheet name="./css/default.css"/>
<h:outputStylesheet name="./css/cssLayout.css"/>
<title>Application><ui:insert name="complementTitreEtH1"/>#{numEx}</title>
</h:head>
<h:body>
<div id="top" class="top">
<h1>Application><ui:insert name="complementTitreEtH1"/>#{numEx}</h1>
</div>
<div>
<div id="left">
<ul>
<li><h:link outcome="/index.xhtml" value="accueil"/></li>
<li><h:link outcome="/formulaire__1.xhtml" value="formulaire_1"/></li>
<li><h:link outcome="/formulaire__2.xhtml" value="formulaire_2"/></li>
<li><h:link outcome="/formulaire__3.xhtml" value="formulaire_3"/></li>
<li><h:link outcome="/formulaire__4.xhtml" value="formulaire_4"/></li>
</ul>
</div>
<div id="content" class="left_content">
<ui:insert name="content">Content</ui:insert>
</div>
</div>
</h:body>
</html>
Remarque : on gardera le code des anciennes pages formulaire et affichage et on met donc 2 "_" avant les nombres.
Vous pouvez voir le rendu de ce template en cliquant droit sur le nom du template à gauche et "Run File".
Ce template définit 4 espaces libres que ses clients pourront spécifier:
Maintenant, nous allons utiliser ce template par défaut :
Pour la page index.xhtml, supprimez l'actuelle page index.xhtml créez une nouvelle page Facelet Template Client (clic droit, New/Java Server Faces/Facelet Template Client), nommée index, qui utilise le template qu'on vient juste de créer, et cochez la valeur "<html> pour "Generate Root Tag". Remplacez le slot nommé complementTitreEtH1 (il ne doit y en avoir qu'un du même nom !) par "Accueil" ; et remplacez le slot nommé content par une description rapide des différents exercice (ou laissez "content" si vous être pressé).
Pour les autre templates créez deux nouveaux Facelet Templates Clients, nommés template_formulaire et template_affichage, qui utilisent le template "template_defaut", et, cette fois-ci de type Generate Root Tag "ui:composition" (la différence avec <html> est que la balise <ui:composition> n'est pas entourée par une balise <html> ; c'est la même chose du point de vue de l'utilisation dans le projet puisque tout ce qui est en dehors de <ui:composition> est ignoré). Les contenus de ces pages sont les suivants:
template_formulaire.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="jakarta.faces.facelets" xmlns:h="jakarta.faces.html" template="./template_defaut.xhtml"> <ui:define name="complementTitreEtH1">formulaire_</ui:define> <ui:define name="content"> <h:form> <h:outputLabel value="Entrez un nombre" for="nombre"/> <h:inputText id="nombre" value="#{bean.nombre}"/> <h:commandButton value="Valider" action="#{bean.actionString('affichage', numEx, query)}"/> </h:form> </ui:define> </ui:composition>
template_affichage.xhtml
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="jakarta.faces.facelets" xmlns:h="jakarta.faces.html" template="./template_defaut.xhtml"> <ui:define name="complementTitreEtH1">affichage_</ui:define> <ui:define name="content"> Les nombres : <br/> <ui:repeat value="#{bean.nombresSuivants}" var="n"> #{n + 1}, </ui:repeat> <p/> <h:link value="Saisir un autre nombre" outcome="#{bean.actionString('formulaire', numEx, query)}"/> </ui:define> </ui:composition>
Ces deux templates spécifient les zones "complementTitreEtH1" et "content", donc les pages formulaire_X.xhtml et affichage_X.xhtml n'auront à priori que le paramètre "numEx" à spécifier, et on fait ça de la manière suivante:
<param name="numEx" value="2"/>
<ui:param name="numEx" value="X"/>
Mais attention, regardez bien: on est obligé de calculer la valeur de l'attribut outcome du <h:link> et action du <h:commandButton> ! On doit donc passer par le backing bean Bean de l'application. Vous implémenterez donc la méthode actionString dans le backing bean, qui doit avoir pour signature:
public String actionString(String base, String numEx, String query);
où:
Travail à faire:
Maintenant si vous avez bien tout suivi jusque là, vous n'avez plus qu'à écrire la méthode actionString dans le backingBean, et vous pourrez réécrire les pages formulaire__X.xhtml (attention, 2 "_" pour garder le code des anciennes pages) comme clientes du template template_formulaire, et les pages affichage__X.xhtml (attention, 2 "_") comme clientes du template template_affichage. Vous pourrez constater à quel point elles sont devenues supersimples !!!
Pour les numeros d'exercices 3 et 4, n'oubliez pas d'ajouter les lignes
<f:metadata> <f:viewParam name="nombre" value="#{bean.nombre}"/> </f:metadata>
lorsque c'est nécessaire !
à vous de jouer !!!