Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Liaison aux données dans vos formulaires JSF

Gestion des événements JSF Internationalisation de pages JSF



Accès rapide :
Liaison d'une structure de données complexe
Le modèle de données à présenter
La liaison aux données dans la vue
Accès à un élément d'un tableau
Utilisation du composant <h:dataTable />

Liaison d'une structure de données complexe

Nous le savons déjà, il est possible de lier nos données, stockées dans nos backing beans, à notre vue via le moteur de liaison aux données, proposé par l'API EL (Expression Language). Comme son nom le laisse entendre, les possibilités de l'EL sont multiples et un langage plus ou moins complexe vous est proposé. Nous l'avons déjà dit dans les chapitres dédiés aux pages JSP : vous pouvez mapper des données complexe à votre vue. Il en va de même pour JSF.

Afin de tester quelques possibilités de l'EL conjointement avec JSF, je vous propose de poursuivre le codage de la page Web proposée dans le chapitre précédent. Elle permettra de parcourir les articles d'un catalogue de vente en ligne et de stocker certains de ces articles dans un panier. Par la suite nous afficherons le contenu du panier. La capture d'écran ci-dessous vous montre à quoi va devoir ressembler la première page Web à développer.

Le modèle de données à présenter

Nous allons, dans un premier temps, nous concentrer sur le modèle de données. Afin de nous simplifier la vie, tout sera stocké en mémoire et il n'y aura donc pas d'accès à une base de données.

quand vous aurez étudié les chapitres relatifs à JPA, vous pourrez charger vos articles à partir d'une base de données en utilisant un mapping JPA. Cela complétera bien cette démonstration.

Je vous propose d'analyser notre première classe. Elle se nomme Catalog et référence tous les articles proposés dans notre catalogue. En voici le code et quelques explications suivront.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
package fr.koor.webstore.business;

import java.util.ArrayList;
import java.util.List;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named
@ApplicationScoped
public class Catalog {

    private List<Article> articles = new ArrayList<>();
    
    public Catalog() {
        articles.add( new Article( 1, "Drone", "Perroquet", 400 ) );
        articles.add( new Article( 2, "Télévision", "SuperBrand", 350 ) );
        articles.add( new Article( 3, "Souris", "Mulot", 35 ) );
        articles.add( new Article( 4, "Smartphone", "MegaMark", 750 ) );
        articles.add( new Article( 5, "Vacances", "DeRêve", 15_000 ) );
    }
    
    public List<Article> getArticles() {
        return articles;
    }
    
    public int getSize() {
        return articles.size();
    }
    
}
Fichier Catalog.java : permet de gérer un catalogue d'articles

Comprenez que ce catalogue est unique en mémoire et est partagé par tous les utilisateurs. C'est pour cette raison que j'ai annoté cette classe comme étant @ApplicationScoped. De plus, cette classe devra être injectée dans le modèle de représentation de chaque utilisateur : d'ou le fait que c'est un objet associé à l'API CDI (Context and Dependency Injection) grâce à l'annotation @Named. Quelques articles ont été ajoutés au catalogue. Voici le code de la classe Article.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
package fr.koor.webstore.business;

public class Article {

    private int idArticle;
    private String description;
    private String brand;
    private double price;
    
    
    public Article() {
        this( 1, "unknown", "unknown", 0 );
    }
    
    
    public Article( int idArticle, String description, String brand, double price ) {
        this.setIdArticle( idArticle );
        this.setDescription( description );
        this.setBrand( brand );
        this.setPrice( price );
    }


    public int getIdArticle() {
        return idArticle;
    }
    
    public void setIdArticle(int idArticle) {
        this.idArticle = idArticle;
    }
        
    public String getDescription() {
        return description;
    }
    
    public void setDescription(String description) {
        this.description = description;
    }
    
    public String getBrand() {
        return brand;
    }
    
    public void setBrand(String brand) {
        this.brand = brand;
    }
    
    public double getPrice() {
        return price;
    }
    
    public void setPrice(double price) {
        this.price = price;
    }
    
    @Override
    public String toString() {
        return "Article [idArticle=" + idArticle + ", description=" + description +
               ", brand=" + brand + ", price=" + price + "]";
    }
    
}
Fichier Article.java : permet de la notion d'article en mémoire

Rien de particulier à noter sur cette classe. La classe suivante, la classe Batch, va nous permettre de représenter un lot d'articles. Un lot d'articles sera défini par la connaisse de l'article associé et de la quantité souhaitée. Voici le code de cette classe.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
package fr.koor.webstore.business;

import java.security.InvalidParameterException;

public class Batch {

    private Article article;
    private int quantity;
        
    public Batch( Article article, int quantity ) {
        if ( article == null )
            throw new NullPointerException( "article cannot be null" );
        if ( quantity < 1 ) 
            throw new InvalidParameterException( "quantity must be a positive number" );
        this.article = article;
        this.quantity = quantity;
    }
    
    public Article getArticle() {
        return article;
    }
    
    public int getQuantity() {
        return quantity;
    }

    public void addOne() {
        quantity++;
    }
    
}
Fichier Batch.java : permet de représenter un lot d'articles

Rien, non plus, de particulier est à remarquer sur cette classe. On peut donc passer à la classe la plus importante : le backing bean qui va être associé à notre formulaire. Voici son code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
package fr.koor.webstore.ihm;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.enterprise.context.SessionScoped;
import javax.faces.event.ActionEvent;
import javax.inject.Inject;
import javax.inject.Named;

import fr.koor.webstore.business.Article;
import fr.koor.webstore.business.Batch;
import fr.koor.webstore.business.Catalog;

@Named
@SessionScoped
public class CatalogBrowserBean implements Serializable {

    private static final long serialVersionUID = 2729758432756108274L;
    
    @Inject
    private Catalog catalog;
    private List<Batch> basket = new ArrayList<>();
    private int index;
    
    
    public Article getCurrentArticle() {
        return catalog.getArticles().get( index );
    }
    
    public List<Batch> getBasket() {
        return basket;
    }
    
    public int getBasketSize() {
        int quantity = 0;
        for( Batch batch : basket ) {
            quantity += batch.getQuantity();
        }
        return quantity;
    }
    
    // --- Event handler methods ---
    
    public void processPreviousAction( ActionEvent event ) {
        if ( --index < 0 ) {
            index = catalog.getSize()-1;
        }
    }
    
    public void processNextAction( ActionEvent event ) {
        if ( ++index >= catalog.getSize() ) {
            index = 0;
        }
    }
    
    public void processAddAction( ActionEvent event ) {
        for( Batch batch : basket ) {
            if ( batch.getArticle().getIdArticle() == getCurrentArticle().getIdArticle() ) {
                batch.addOne();
                return;
            }
        }
        basket.add( new Batch( getCurrentArticle(), 1 ) );
    }

}
Fichier CatalogBrowserBean : la classe de backing bean de notre formulaire

En ligne 23, vous constaterez que notre instance de catalogue est injectée (via le framework CDI) dans notre backing bean. En ligne 28, la méthode getCurrentArticle utilise l'attribut index (ligne 25) pour retrouver l'article à afficher dans le catalogue (index représentant la position dans le tableau). La méthode getBasket nous servira plus tard dans ce chapitre pour afficher l'intégralité des articles stockés dans le panier. En ligne 36, la méthode getBasketSize renvoie le nombre d'articles, en tenant compte des quantités de chaque lot, stockés dans le panier.

A partir de la ligne 44, vous retrouverez les trois gestionnaires d'événements associés à nos trois boutons. Les codes de ces trois gestionnaires d'événements sont assez simples : vous ne devriez pas rencontrer de problème de compréhension.

La liaison aux données dans la vue

Il est maintenant temps de passer à la vue qui, grâce à un ensemble d'expressions EL, va être liée au backing bean présenté ci-dessus. En voici son code.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
<!DOCTYPE html>
<html xmlns:f="http://xmlns.jcp.org/jsf/core" 
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <f:view>
        <head>
            <title>View Article</title>
            <link rel="stylesheet" type="text/css" href="styles.css" />
        </head>
        <body>
            <h1>View Article</h1>
                
            <h:form>
                Identifiant : #{catalogBrowserBean.currentArticle.idArticle} <br/>
                Description : #{catalogBrowserBean.currentArticle.description} <br/>
                Marque : #{catalogBrowserBean.currentArticle.brand} <br/>
                Prix : #{catalogBrowserBean.currentArticle.price} <br/>
                
                <br/>
                
                <h:commandButton value="Précédent" 
                                 actionListener="#{catalogBrowserBean.processPreviousAction}" /> &#160;
                <h:commandButton value="Ajouter au panier" 
                                 actionListener="#{catalogBrowserBean.processAddAction}" /> &#160;
                <h:commandButton value="Suivant" 
                                 actionListener="#{catalogBrowserBean.processNextAction}" />
                
                <br/>
                
                Vous avez #{catalogBrowserBean.basketSize} article(s) dans votre panier.
                <br/>
                <a href="summary.xhtml">Voir le contenu du panier</a>
            </h:form>
        
        </body>
    </f:view>
</html>
viewArticle.xhtml : la déclaration de la facelet associé à la vue

Les lignes 13 à 16 montrent que l'API EL peut manipuler une hiérarchie d'objets. Dans notre cas, le backing bean possède une propriété currentArticle (via la méthode getCurrentArticle) qui calcule un résultat de type Article. Du coup, l'EL permet de descendre de nouveau dans les propriétés de l'article : par exemple, catalogBrowserBean.currentArticle.description.

Les lignes 20 à 25 montre, mais nous en avons déjà parlé, que l'EL est capable de lier des gestionnaires d'événements côté serveur à vos boutons. Je vous renvoie vers le chapitre précédent pour de plus amples informations à ce sujet.

Accès à un élément d'un tableau

Il est possible, via l'EL, d'accéder à un élément particulier d'une collection par son indice. Voici un exemple d'expression permettant ce type d'accès.

 1 
 2 
 3 
 4 
<div>
    Prix du premier article du panier :
    #{catalogBrowserBean.basket[0].article.price} euros
</div>
Accès à un bean dans une collection

Utilisation du composant <h:dataTable />

Nous allons maintenant mettre en oeuvre une nouvelle page JSF dont l'objectif est de présenter l'ensemble des articles sélectionnés dans le panier. Pour rappel, le panier correspond à l'attribut basket du « backing bean » nommé catalogBrowserBean et il est de type List<Batch>. Pour lier une collection à votre page web vous pouvez utiliser le composant <h:dataTable />.

Le composant <h:dataTable /> va produire un tableau (comme son nom l'indique) avec une ligne de titre et autant de ligne de données que d'éléments dans la collection considérée. Dans notre cas, la collection correspondra à notre panier : chaque élément de la collection sera donc une instance de la classe Batch. Un lot d'articles (batch en anglais) étant associé à un article, lui-même constitué de quatre attributs, et à une quantité, notre tableau aura donc 5 colonnes : identifiant de l'article, description, marque, prix et quantité.

Voici donc le code de la facelet permettant d'afficher le contenu du panier.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
<!DOCTYPE html>
<html xmlns:f="http://xmlns.jcp.org/jsf/core" 
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <f:view>
        <head>
            <title>Contenu du panier</title>
        </head>
        <body>
            <h1 align="center">Contenu du panier</h1>

            <h:dataTable value="#{catalogBrowserBean.basket}" var="batch"
                         style="width: 60%; margin: auto;">
                <h:column>
                    <f:facet name="header">Identifiant</f:facet>
                    #{batch.article.idArticle} 
                </h:column>
                <h:column>
                    <f:facet name="header">Marque</f:facet>
                    #{batch.article.brand}
                </h:column>
                <h:column>
                    <f:facet name="header">Description</f:facet>
                    #{batch.article.description}
                </h:column>
                <h:column>
                    <f:facet name="header">Prix Unitaire</f:facet>
                    #{batch.article.price} &#8364;
                </h:column>
                <h:column>
                    <f:facet name="header">Quantité</f:facet>
                    #{batch.quantity}
                </h:column>
            </h:dataTable>
                        
            <br/>
            <a href="viewArticle.xhtml">Retour au catalogue</a>
            
        </body>
    </f:view>
</html>
La facelet summary.xhtml

Et voici un résultat visuel produit par cette page.

Il est possible de décorer cette table de données en utilisant quelques classes de styles CSS. Je pense que l'exemple ci-dessous parle de lui-même quant-à l'intérêt de chaque classe de styles.

.dataTable {
    border: 1px solid black;
    border-spacing : 1px;
    border-collapse : collapse;
    margin: auto;
    width: 60%;
}

.dataTable thead {
    background: #888a85;
}

.dataTable .even {
    background: #eeeeec;
}

.dataTable .odd {
    background: #d3d7cf;
}
Fichier styles.css

Et voici le code de la facelet utilisant les classes de styles proposées ci-dessus.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
<!DOCTYPE html>
<html xmlns:f="http://xmlns.jcp.org/jsf/core" 
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <f:view>
        <head>
            <title>Contenu du panier</title>
            <link rel="stylesheet" type="text/css" href="../styles.css" />
        </head>
        <body>
            <h1>Contenu du panier</h1>

            <h:dataTable value="#{catalogBrowserBean.basket}" var="batch"
                         styleClass="dataTable" rowClasses="even,odd">
                <h:column>
                    <f:facet name="header">Identifiant</f:facet>
                    #{batch.article.idArticle} 
                </h:column>
                <h:column>
                    <f:facet name="header">Marque</f:facet>
                    #{batch.article.brand}
                </h:column>
                <h:column>
                    <f:facet name="header">Description</f:facet>
                    #{batch.article.description}
                </h:column>
                <h:column>
                    <f:facet name="header">Prix Unitaire</f:facet>
                    #{batch.article.price} &#8364;
                </h:column>
                <h:column>
                    <f:facet name="header">Quantité</f:facet>
                    #{batch.quantity}
                </h:column>
            </h:dataTable>
                        
            <br/>
            <a href="viewArticle.xhtml">Retour au catalogue</a>
            
        </body>
    </f:view>
</html>
La facelet summary.xhtml avec un peu de styles CSS
L'attribut rowClasses permet de spécifier autant de classes de styles que désiré (dans notre cas deux). Les noms des classes doivent être séparés par une virgule. Ces classes de styles seront appliquées à tour de rôle et de manière cyclique sur chaque ligne de résultats produite.

Voici le résultat visuel produit par cet exemple.



Gestion des événements JSF Internationalisation de pages JSF