Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Afficher des tableaux sexy avec Wicket

Les tableaux HTML sont plus lisibles lorsque les lignes paires sont facilement différenciables des lignes impaires, par exemple grâce à un fond de couleur différente.
Voyons comment Wicket permet d'industrialiser ce traitement, afin qu'il soit facilement applicable à n'importe quel tableau.

Pour l'exemple, nous afficherons la liste des Locales suportées par la JVM (langage et pays).

Afficher un tableau avec Wicket

Tout d'abord, rappelons comment on affiche un tableau en Wicket.

Du point de vue HTML, il suffit d'utiliser les balises standard : <table>, <tr>, <td>.
Comme c'est l'élément <tr> (et tout son contenu) qui sera répété pour chaque Locale à afficher, c'est lui qui porte l'attribut "wicket:id".

  1. <table>
  2. <tr>
  3. <th>Country</th>
  4. <th>Language</th>
  5. </tr>
  6. <tr wicket:id="localesList">
  7. <td wicket:id="localeCountry">Kronos</td>
  8. <td wicket:id="localeLanguage">Klingon</td>
  9. </tr>
  10. </table>

Du point de vue Java, ce n'est guère plus compliqué grâce au composant ListView.
Comme tout composant Wicket, il respecte l'architecture MVC :

  • Un Modèle (IModel) doit lui être fourni - ici, la liste des Locales disponibles ;
  • Le ListView lui-même encapsule la logique du Contrôleur, en bouclant sur les éléments du Modèle ;
  • La Vue est construite dynamiquement par la méthode populateItem(), sur laquelle nous nous concentrerons plus loin.

Voici donc le squelette de notre Page :

  1. public class StripedTablePage extends WebPage {
  2.  
  3. /** A list of Locales */
  4. List<Locale> locales = Arrays.asList(Locale.getAvailableLocales());
  5.  
  6. /** Constructor */
  7. public StripedTablePage() {
  8.  
  9. ListView<Locale> localesList = new ListView<Locale>("localesList", locales) {
  10. @Override
  11. protected void populateItem(final ListItem<Locale> item) {
  12. // TODO : build the View
  13. }
  14. };
  15. add(localesList);
  16. }
  17.  
  18. }

Le paramètre "item" de la méthode "populateItem()" a deux utilités :

  • Il donne accès à l'élément courant : Object element = item.getModelObject()) ;
  • Il représente la racine des composants de la Vue, qui doivent donc y être attachés : item.add(new Label(...)).

Dans notre cas, deux Labels servent à afficher la langue et le pays de chaque Locale. Voici donc la méthode complétée :

  1. @Override
  2. protected void populateItem(final ListItem<Locale> item) {
  3. // Retrieve the current Locale
  4. final Locale loc = item.getModelObject();
  5.  
  6. // Add a Label for the Country
  7. item.add(new Label("localeCountry", new AbstractReadOnlyModel<String>() {
  8. @Override
  9. public String getObject() {
  10. return loc.getDisplayCountry(loc);
  11. }
  12. }));
  13.  
  14. // Add a Label for the Language
  15. item.add(new Label("localeLanguage", new AbstractReadOnlyModel<String>() {
  16. @Override
  17. public String getObject() {
  18. return loc.getDisplayLanguage(loc);
  19. }
  20. }));
  21. }

Amélioration du rendu avec les styles CSS

Afin de différencier les lignes paires et impaires, nous allons leur associer des classes CSS différentes :

  1. .evenRow {background-color: #FFF;}
  2. .oddRow {background-color: #DDF;}

Mais comment les appliquer aux balises <tr> ?
On pourrait éventuellement sous-classer le composant ListView pour contrôler le code HTML généré, mais cette solution est techniquement complexe et peu souple.
Nous utiliserons plutôt le système de "Behaviors" de Wicket : implémentant le design pattern Decorator, un Behavior encapsule un traitement particulier et peut être facilement appliqué à tout composant compatible.

Wicket en fournit justement deux, spécialisés dans la manipulation des attributs des balises HTML : AttributeModifier et AttributeAppender. Nous nous appuierons sur ce dernier pour ajouter la bonne classe CSS nos balises <tr>.

Pour fonctionner, notre Behavior a besoin du numéro de la ligne courante et des noms des classes CSS à appliquer. Tout le traitement proprement dit est réalisé par l'AttributeAppender dont on hérite :

  1. public class AlternateRowCssClassAttributeAppender extends AttributeAppender {
  2.  
  3. public static final String ATTRIBUTE_NAME = "class";
  4. public static final String VALUE_SEPARATOR = " ";
  5.  
  6. public AlternateRowCssClassAttributeAppender(final int index, final String evenRowCssClass, final String oddRowCssClass) {
  7. super(
  8. ATTRIBUTE_NAME, true,
  9. new AbstractReadOnlyModel<String>() {
  10. @Override
  11. public String getObject() {
  12. // Our algorithm
  13. return (index % 2 == 0) ? evenRowCssClass : oddRowCssClass;
  14. }
  15. },
  16. VALUE_SEPARATOR
  17. );
  18. }
  19.  
  20. }

Pour finir, il reste à l'appliquer à chaque ligne du tableau, lors de la construction de la Vue :

  1. @Override
  2. protected void populateItem(final ListItem<Locale> item) {
  3. // Retrieve the current Locale and add the Labels
  4. (...)
  5. // Apply the Behavior to colorize each other line
  6. item.add(new AlternateRowCssClassAttributeAppender(item.getIndex(), "evenRow", "oddRow"));
  7. }

Le code complet de notre page d'exemple devient donc :

  1. public class StripedTablePage extends WebPage {
  2.  
  3. /** A list of Locales */
  4. List<Locale> locales = Arrays.asList(Locale.getAvailableLocales());
  5.  
  6. /** Constructor */
  7. public StripedTablePage() {
  8. ListView<Locale> localesList = new ListView<Locale>("localesList", locales) {
  9. @Override
  10. protected void populateItem(final ListItem<Locale> item) {
  11. // Apply the Behavior to colorize each other line
  12. item.add(new AlternateRowCssClassAttributeAppender(item.getIndex(), "evenRow", "oddRow"));
  13.  
  14. // Retrieve the current Locale
  15. final Locale loc = item.getModelObject();
  16.  
  17. // Add a Label for the Country
  18. item.add(new Label("localeCountry", new AbstractReadOnlyModel<String>() {
  19. @Override
  20. public String getObject() {
  21. return loc.getDisplayCountry(loc);
  22. }
  23. }));
  24.  
  25. // Add a Label for the Language
  26. item.add(new Label("localeLanguage", new AbstractReadOnlyModel<String>() {
  27. @Override
  28. public String getObject() {
  29. return loc.getDisplayLanguage(loc);
  30. }
  31. }));
  32. }
  33.  
  34. };
  35. add(localesList);
  36. }
  37.  
  38. }

Conclusion

Après avoir revu la façon dont Wicket gérait les tableaux, nous avons développé un Behavior personnalisé permettant d'appliquer des styles CSS différents aux lignes paires et impaires.
Vous pouvez désormais l'ajouter à votre librairie de composants réutilisables !

Le code source de l'article est disponible en pièce jointe de ce billet.
Le script Gradle fourni permet de le compiler et de lancer un serveur Jetty intégré :

  1. gradle jettyRun

L'application est alors accessible à l'adresse suivante : http://localhost:8080/Wicket-tips/


Commentaires

1. Le mardi 6 octobre 2009, 13:19 par Olivier

J'ai eu la même problèmatique, sauf que dans mon cas j'ai utilisé le Datatable des wicket-extensions : il suffit de lui fournir un SortableDataProvider en entrée et ça s'occupe de pratiquement tout (tri, pagination, etc...).

Pour obtenir des stripes il suffit de surcharger les providers Cell et Row. En gros ça donne ça dans le principe :

Datatable dataTable = new Datatable("datatable", columns, dataProvider, 30) {
private static final long serialVersionUID = 1L;

@Override
protected Item newRowItem(String id, int index, IModel model) {
return new CustomOddEvenRow(id, index, model);
}

@Override
protected Item newCellItem(String id, int index, IModel model) {
return new CustomOddEvenCell(id, index, model);
}
};
add(dataTable);

...

protected class CustomOddEvenRow extends OddEvenItem {
private static final long serialVersionUID = 1L;

public CustomOddEvenRow(String id, int index, IModel model) {
super(id, index, model);
}

@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
tag.put("class", (getIndex() % 2 == 0) ? "oddr" : "evenr");
}

}

protected class CustomOddEvenCell extends OddEvenItem {
private static final long serialVersionUID = 1L;

public CustomOddEvenCell(String id, int index, IModel model) {
super(id, index, model);
}

@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
tag.put("class", (getIndex() % 2 == 0) ? "evenc" : "oddc");
}
}

D'ailleurs je me rends compte que j'ai écrit ça a mes débuts puisque le tag.put() devrait plutôt être un Behaviour appender :

tag.add(new AttributeAppender("class", new Model<String>("evenr"), " "));

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.