sept.
2009
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
".
<table> <tr> <th>Country</th> <th>Language</th> </tr> <tr wicket:id="localesList"> <td wicket:id="localeCountry">Kronos</td> <td wicket:id="localeLanguage">Klingon</td> </tr> </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 :
public class StripedTablePage extends WebPage { /** A list of Locales */ List<Locale> locales = Arrays.asList(Locale.getAvailableLocales()); /** Constructor */ public StripedTablePage() { ListView<Locale> localesList = new ListView<Locale>("localesList", locales) { @Override protected void populateItem(final ListItem<Locale> item) { // TODO : build the View } }; add(localesList); } }
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 :
@Override protected void populateItem(final ListItem<Locale> item) { // Retrieve the current Locale final Locale loc = item.getModelObject(); // Add a Label for the Country item.add(new Label("localeCountry", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return loc.getDisplayCountry(loc); } })); // Add a Label for the Language item.add(new Label("localeLanguage", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return loc.getDisplayLanguage(loc); } })); }
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 :
.evenRow {background-color: #FFF;} .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 :
public class AlternateRowCssClassAttributeAppender extends AttributeAppender { public static final String ATTRIBUTE_NAME = "class"; public static final String VALUE_SEPARATOR = " "; public AlternateRowCssClassAttributeAppender(final int index, final String evenRowCssClass, final String oddRowCssClass) { super( ATTRIBUTE_NAME, true, new AbstractReadOnlyModel<String>() { @Override public String getObject() { // Our algorithm return (index % 2 == 0) ? evenRowCssClass : oddRowCssClass; } }, VALUE_SEPARATOR ); } }
Pour finir, il reste à l'appliquer à chaque ligne du tableau, lors de la construction de la Vue :
@Override protected void populateItem(final ListItem<Locale> item) { // Retrieve the current Locale and add the Labels (...) // Apply the Behavior to colorize each other line item.add(new AlternateRowCssClassAttributeAppender(item.getIndex(), "evenRow", "oddRow")); }
Le code complet de notre page d'exemple devient donc :
public class StripedTablePage extends WebPage { /** A list of Locales */ List<Locale> locales = Arrays.asList(Locale.getAvailableLocales()); /** Constructor */ public StripedTablePage() { ListView<Locale> localesList = new ListView<Locale>("localesList", locales) { @Override protected void populateItem(final ListItem<Locale> item) { // Apply the Behavior to colorize each other line item.add(new AlternateRowCssClassAttributeAppender(item.getIndex(), "evenRow", "oddRow")); // Retrieve the current Locale final Locale loc = item.getModelObject(); // Add a Label for the Country item.add(new Label("localeCountry", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return loc.getDisplayCountry(loc); } })); // Add a Label for the Language item.add(new Label("localeLanguage", new AbstractReadOnlyModel<String>() { @Override public String getObject() { return loc.getDisplayLanguage(loc); } })); } }; add(localesList); } }
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é :
gradle jettyRun
L'application est alors accessible à l'adresse suivante : http://localhost:8080/Wicket-tips/
Commentaires
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"), " "));