TP 2 - PRG avec JSF

Retour TPs

Table des matières
  1. Objectifs et présentation du TP
  2. Création du projet
  3. Exercice 1 : analyse d'un post tout simple
  4. Exercice 2 : utilisation de la redirection (modèle PRG)
  5. Exercice 3 : utilisation des paramètres de vue
  6. Exercice 4 : idem mais...

Objectifs et présentation du TP

Exercices pour comprendre le modèle de navigation de JSF et le modèle PRG.

  1. Présenter les problèmes du POST, remarqués à la fin du TP1,
  2. Présenter le modèle PRG qui permet d'éviter la double soumission des formulaires et de pouvoir garder un marque-page/favori/bookmark des pages affichées.

Remarque sur la portée des beans CDI : dans ce TP vous utiliserez des backing beans de portée "Requête". Il faut toujours essayer de limiter au maximum la portée des beans pour éviter l'encombrement de la mémoire du serveur Web (ce qui peut être un problème pour les sites Web très fréquentés). Évidemment dans certains cas, les portées plus étendues comme les portées "Vue", "Conversation", ou même "Session" sont plus indiquées (par exemple la portée "Session" pour un backing bean qui gère des informations liées à l'utilisateur en cours).

Nous allons aussi repecter cette règle : utiliser une requête POST pour modifier des données sur le serveur et utiliser une requête GET si aucune donnée du serveur n'est modifiée. Ce principe peut être contourné mais il vaut mieux s'y tenir.

Création du projet

Un même projet servira pour tout le TP et aussi pour le TP suivant sur les templates.

Comme pour le TP 1 créez un nouveau projet de type "Java with Maven" >  "Web Application". Le serveur sera toujours Payara et la version de Jakarta EE sera toujours 10.

Allez voir le fichier web.xml dans "Configuration Files". Faites les mêmes modifications que dans le TP 1. La page "welcome" ne sera pas utilisée et vous pouvez laisser la valeur par défaut. Aucune base de données ne sera utilisée.

Faites aussi les mêmes modifications que pour le TP 1 dans pom.xml pour mettre à jour les plugins.

Ajoutez aussi la classe de configuration de JSF.

Rappel : n'oubliez pas les commits, et les pushs sur GitHub. Au moins un commit par exercice.


Exercice 1 : analyse d'un post tout simple

Nous allons étudier une application qui contient 2 pages JSF qui se référencent :

Respectez bien ces noms de fichier car ils seront utilisés dans le TP suivant sur les templates.

Clic droit sur le projet (dans le volet des projets à gauche) et "New > JSF Page" pour créer les pages. Pour les noms des pages, ne tapez pas le suffixe ".xhtml" car il sera ajouté automatiquement par NetBeans. Mettez les pages directement sous la racine des pages Web (laissez vide la champ "Folder").

Modifiez le code généré pour avoir les deux pages ci-dessous :

formulaire_1.xhtml 

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html">

    <h:head>
        <title>Application&gt;formulaire_1</title>
    </h:head>
    <h:body>

     <h1>Application&gt;formulaire_1</h1>

     <h:form>
        <h:outputLabel value="Entrez un nombre" for="nombre"/>
        <h:inputText id="nombre" value="#{bean.nombre}"/>
        <h:commandButton value="Valider" action="affichage_1"/>
      </h:form>

    </h:body>
</html>

affichage_1.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets">
    <h:head>
        <title>Application&gt;affichage_1</title>
    </h:head>

    <h:body>
        <h1>Application&gt;affichage_1</h1>
        Les nombres : <br/>
        <ui:repeat value="#{bean.nombresSuivants}" var="n" varStatus="status">
            #{n + 1}#{! status.last ? ', ' : ''}
        </ui:repeat>
        <h:form>
            <h:commandLink action="formulaire_1" value="Saisir un autre nombre"/>
        </h:form>
    </h:body>
</html>

Comme toutes les pages qui vous sont données dans ce TP, vous devez les lire et en comprendre le code.

Vous devinez à quoi sert l'attribut varStatus de <ui:repeat>, utilisé dans l'expression EL #{! status.last ? ', ' : ''} ?

<ui:repeat> correspond à une boucle qui parcourt la structure donnée par l'attribut value (c'est une liste de nombres entiers ; voir code du backing bean ci-dessous) et dont la variable de boucle est "n". La structure "xxx ? yyy : zzz" est semblable à celle que l'on trouve dans le langage C ou dans Java.

Ces pages utilisent un backing bean (son nom est "bean") pour stocker la propriété correspondant au champ de formulaire "nombre" :

<h:inputText id="nombre" value="#{bean.nombre}"/>

Comme dans le TP 1, créez le backing bean suivant dans le paquetage xx.xxxx.xxxx.jsf, sous le package racine du projet (pas comme dans la capture d'écran ci-dessous), avec la portée "request" (clic droit sur le projet et New > JSF CDI Bean) :

Managed bean

Modifiez le code généré pour avoir le code suivant :

Bean.java :

package xx.xxx.xxxx.jsf; // à modifier

import java.util.ArrayList;
import java.util.List;
import jakarta.inject.Named;
import jakarta.enterprise.context.RequestScoped;

@Named(value = "bean")
@RequestScoped
public class Bean {

    private int nombre;

    public int getNombre() {
        return nombre;
    }

    public void setNombre(int nombre) {
        this.nombre = nombre;
    }

    public List<Integer> getNombresSuivants() {
        int nb = 5;
        List<Integer> l = new ArrayList<>(nb);
        for (int i = nombre; i < nombre + nb; i++) {
            l.add(i);
        }
        return l;
    }
}

Regardez déjà comment cet exemple s'articule. Notez dans les pages JSF l'utilisation de <ui:repeat ... />. Notez que la collection sur laquelle on itère, donnée par :

#{bean.nombresSuivants}

correspond à un accès à la propriété nombresSuivants qui existe puisqu'une méthode getNombresSuivants() existe. Rappelons que ce sont les getters et setters qui définissent une propriété et non pas l'existence d'une variable d'instance dans la classe. En particulier ici on n'a pas de variable d'instance.

Exécutez cet exemple :

Une amélioration

Le lien de la page affichage_1 vers la page formulaire_1 ne modifie rien sur le serveur et n'utilise aucune information saisie par l'utilisateur. Selon la règle énoncée au début du TP, il vaudrait mieux lancer une requête GET qu'une requête POST. Pour cela il faut remplacer le composant <h:commandLink> par un autre composant. Lequel ? Testez. Est-ce que vous voyez une différence dans les URLs affichés par le navigateur ? Réponse.

Quelques problèmes

Essayez de faire afficher au début dans la zone de saisie le nombre entré précédemment quand vous saisissez un nouveau nombre (ne cherchez pas trop...). Réponse.

Est-ce possible de mettre un bookmark/marque page sur la page affichage_1 pour un certain nombre ? Par exemple si vous avez tapé 5, la page affichage_1 affichera les nombres 6 à 10 qui sont censés représenter une information importante sur le nombre 5. Essayez de garder un bookmark sur cette page.

Et oui, cela ne marche pas (il suffit de voir l'URL de la page pour le comprendre) !

La suite de ce TP étudie un modèle, appelé PRG, pour ne plus avoir ce genre de problème.


Exercice 2 : utilisation de la redirection (modèle PRG)

On va commencer par régler le problème de l'URL en utilisant une redirection.

Dans cet exercice et les suivants, pour conserver les versions successives, formulaire_1 va être formulaire_X (X= 2, puis 3, puis 4) et affichage_1 va être affichage_X.

Dans l'exercice 1, la page affichée après la soumission du formulaire est la page réponse de la requête POST qui soumet le formulaire. JSF valide les formulaires avec une requête POST.

Dans cet exercice, au lieu de retourner directement affichage_2 en réponse à la requête POST de soumission du formulaire, la réponse à la soumission du formulaire sera un ordre de redirection vers affichage_2. Lorsqu'il va recevoir cet ordre de redirection (à la place de la page), le navigateur va lancer une requête GET vers affichage_2. Le navigateur (qui a lui-même lancé la requête GET) pourra ainsi afficher le bon URL.

Pour utiliser une redirection il suffit d'ajouter "?faces-redirect=true" aux URLs dans les pages JSF.

Pour tester, lancez formulaire_2 : clic droit sur la page formulaire_2.xhtml et "Run File".

Est-ce que les URL sont corrects maintenant ?

Rechargez la page qui affiche les nombres. Avez-vous toujours le problème de l'exercice précédent quand vous rechargez la page d'affichage (double soumission du formulaire) ?

Malheureusement les bons nombres ne sont pas affichés ! Expliquez pourquoi. Réponse.

Tout marche donc bien, sauf le principal ;-) Tout sera réglé dans le prochain exercice.


Exercice 3 : utilisation des paramètres de vue

Vous allez utiliser les paramètres de vue étudiés en cours pour afficher les bons nombres. 

Copiez les fichiers "_2" pour obtenir les fichiers formulaire_3.xhtml et affichage_3.xhtml qui seront utilisés dans cet exercice. N'oubliez pas de changer les "2" en "3" là où il le faut.

Comment passer la valeur de la propriété "nombre" d'une instance du backing bean à une autre (n'oubliez pas que la portée du bean est la requête et ce sont donc 2 instances différentes de la classe Bean qui sont utilisées par les 2 pages JSF quand il y a une redirection) ?

Vous allez utiliser un paramètre de vue (comme dans le TP 1) :

Méthode "action" afficher du bean :

public String afficher() {
    return "affichage_3?nb="+ nombre + "&amp;faces-redirect=true";
}

("&" marche aussi à la place de "&amp;")

formulaire_3.xhtml :

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html">

    <h:head>
        <title>Application&gt;formulaire_3</title>
    </h:head>
    <h:body>

     <h1>Applicationformulaire_3</h1>

     <h:form>
        <h:outputLabel value="Entrez un nombre" for="nombre"/>
        <h:inputText id="nombre" value="#{bean.nombre}"/>
        <h:commandButton value="Valider" action="#{bean.afficher()}"/>
      </h:form>
    </h:body>
</html>

Ainsi la page qui affiche les nombres pourra être ajoutée dans les marque pages du navigateur.

Dans affichage_3.xhtml on ajoute un paramètre de vue pour récupérer le nombre saisi par l'utilisateur. Lisez bien le code de <f:metadata> et de la méthode afficher() pour comprendre ce qui se passe.

affichage_3.xhtml :

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="jakarta.faces.html"
      xmlns:ui="jakarta.faces.facelets"
      xmlns:f="jakarta.faces.core">

    <f:metadata>
        <f:viewParam name="nb" value="#{bean.nombre}"/>
    </f:metadata>

    <h:head>
        <title>Applicationaffichage_3</title>
    </h:head>
    <h:body>

     <h1>Application&gt;affichage_3</h1>

        Les nombres : <br/>
        <ui:repeat value="#{bean.nombresSuivants}" var="n" varStatus="status">
            #{n + 1}#{! status.last ? ', ' : ''}
        </ui:repeat>
        <p/>
        <h:link outcome="formulaire_3" value="Saisir un autre nombre"/>
    </h:body>     
</html>

Testez votre application. Étudiez en particulier les URL affichés par le navigateur.

Est-ce possible de garder un marque-page pour garder, par exemple, les 5 nombres qui suivent le nombre 34 ? Réponse.

Tous les problèmes sont donc résolus grâce au modèle PRG.


Exercice 4 : idem mais...

Modification très simple de l'exercice 3: Reprenez le code de l'exercice 3, mettez le suffixe 4 aux pages utilisées par cet exercice : formulaire_4.xhtml et affichage_4.xhtml.

Montrez que vous avez compris : ajoutez simplement le fait que le nombre précédemment saisi apparaisse par défaut dans le formulaire lorsqu'on veut saisir un autre nombre (dans l'exercice 3, "0" est toujours affiché).

N'écrasez pas le bean, copiez la classe Bean et changez ce qu'il faut dans le bean (la valeur retour de la méthode afficher() et aussi la valeur dans @Named) et dans les pages pour référencer le nouveau bean.

Revoyez dans le cours comment ajouter un paramètre dans l'URL généré par <h:link>.

Si ça ne marche pas c'est sans doute que vous avez oublié de modifier des "3" en "4".

Réponse

Retour TPs