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 />
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.
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.
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(); } } |
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 + "]"; } } |
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++; } } |
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 ) ); } } |
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.
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}" />   <h:commandButton value="Ajouter au panier" actionListener="#{catalogBrowserBean.processAddAction}" />   <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> |
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.
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> |
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} € </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> |
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; }
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} € </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> |
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.
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 :