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

Prochaines sessions inter-entreprises : 28-31 mars 2017 / 13-16 juin 2017
Sessions intra-entreprises sur demande.
Inscrivez-vous vite !

Wicket : une astuce pour mieux gérer ses styles CSS

Lorsque vous développez des pages web, si vous suivez les bonnes pratiques (n'est-ce pas ?), vous nommez vos identifiants CSS en fonction des données qu'ils représentent ("selectedCountry", "clientList") et non selon leur représentation visuelle ("greyBackgroundBlueText", "redBorder").
Mais il arrive fréquemment qu'une même donnée doive être représentée différemment en fonction de la page sur laquelle elle apparaît. Comment associer un style différent à un même identifiant logique en fonction de son contexte d'affichage ?

Bien sûr, il est toujours possible de contourner le problème en associant plusieurs identifiants à une même information logique, mais l'on perd alors en précision sémantique.
L'astuce que je vous propose ici résout le problème de manière plus satisfaisante, en utilisant les sélecteurs hiérarchiques CSS de manière similaire aux namespaces XML. Si cette technique peut être appliquée à tout site ou application web. le but de ce billet est de montrer comment Wicket simplifie sa mise en oeuvre.

La technique

Tout d'abord, pour différencier les pages les unes des autres, il faut associer à chacune un identifiant unique. Pour cela, le plus simple est d'associer un identifiant CSS (id) à leur balise <body> (ou à défaut, à un <div> placé juste après) :

  1. <html>
  2. <body id="bluePage">
  3. Hello <span class="worldColor">blue</span> world !
  4. </body>
  5. </html>

Ensuite, il suffit de tirer parti des propriétés hiérarchiques des sélecteurs CSS pour désigner de manière absolue les éléments qui composent les pages :

  1. #bluePage .worldColor {color: blue;}
  2. #redPage .worldColor {color: red;}
  3. (...)

Personnellement, je considère cette technique comme une bonne pratique. Elle permet de documenter le code CSS, et augmente la confiance de l'équipe de développement en sa capacité à le comprendre et le faire évoluer, car les impacts potentiels sont immédiatement identifiables.
Mais elle demande une certaine discipline, qui peut malencontreusement être perdue au fil du temps.

Voyons comment Wicket permet de garantir son application.

L'application d'exemple

L'application d'exemple est composée de deux pages (BluePage et RedPage) affichant le fameux message "Hello World", revisité en couleurs pour l'occasion, et d'une page template permettant de passer de l'une à l'autre à l'aide de liens.

Voici le code de la page template :

  1. <html>
  2. <head>
  3. <link rel="stylesheet" type="text/css" href="css/style.css"/>
  4. <title>Wicket-tips-css</title>
  5. </head>
  6.  
  7. <body>
  8. Choose your color :
  9. <a href="#" wicket:id="bluePageLink">Blue</a> |
  10. <a href="#" wicket:id="redPageLink">Red</a>
  11. <wicket:child/>
  12. </body>
  13. </html>
  1. public class BasePage extends WebPage {
  2. public BasePage() {
  3. add(new BookmarkablePageLink<Void>("bluePageLink", BluePage.class));
  4. add(new BookmarkablePageLink<Void>("redPageLink", RedPage.class));
  5. }
  6. }

Et celui d'une des deux pages :

  1. <wicket:extend>
  2. Hello <span class="worldColor">blue</span> world !
  3. </wicket:extend>
  1. public class BluePage extends BasePage {
  2. }

Intégration de la technique CSS

Le problème de l'emplacement de l'identifiant

Etant donné que l'application utilise une page template, où placer notre ID unique ?

Une solution pourrait être de placer un <div> juste à l'intérieur des balises <wicket:extend> :

  1. <wicket:extend>
  2. <div id="bluePage">
  3. Hello <span class="worldColor">blue</span> world !
  4. </div>
  5. </wicket:extend>

Mais cela ne nous permettrait pas de styler différemment les éléments du template lui-même, situé en-dehors.
Il nous faut donc placer l'identifiant au niveau de la balise <body> du template, comme vu plus haut ; mais dans ce cas, comment modifier dynamiquement son identifiant en fonction de la page fille affichée ?

  1. <html>
  2. <body id="???">
  3. Choose your color :
  4. <a href="#" wicket:id="bluePageLink">Blue</a> |
  5. <a href="#" wicket:id="redPageLink">Red</a>
  6. <wicket:child/>
  7. </body>
  8. </html>

Modification dynamique de l'ID

Pour y parvenir, commençons par déclarer la balise body du template comme un WebMarkupContainer. C'est un composant Wicket très basique servant simplement à englober un fragment de page HTML.

  1. <html>
  2. <body wicket:id="pageBody">
  3. Choose your color : <a href="#" wicket:id="bluePageLink">Blue</a> | <a href="#" wicket:id="redPageLink">Red</a>
  4. <wicket:child/>
  5. </body>
  6. </html>
  1. public class BasePage extends WebPage {
  2. public BasePage() {
  3. // Links
  4. add(new BookmarkablePageLink<Void>("bluePageLink", BluePage.class));
  5. add(new BookmarkablePageLink<Void>("redPageLink", RedPage.class));
  6. // Page wrapper, for CSS management
  7. WebMarkupContainer container = new WebMarkupContainer("pageBody");
  8. add(container);
  9. }
  10. }

Pour modifier dynamiquement son attribut "id", il suffit d'appliquer un AttributeModifier à notre WebMarkupContainer.
Ici, par convention, nous utiliserons le nom de la classe représentant la page comme identifiant CSS. Naturellement, il serait possible d'appeler à la place une méthode de la classe fille afin qu'elle fournisse elle-même son identifiant ; mais j'aime ce côté automatique, "convention over configuration", qui limite les risques d'erreur humaine.

  1. WebMarkupContainer container = new WebMarkupContainer("pageBody");
  2. add(container);
  3. String pageId = getClass().getSimpleName();
  4. container.add(new AttributeModifier("id", new Model<String>(pageId)));

Le respect de la hiérarchie

Il nous reste à régler un dernier point pour que tout ce système fonctionne.

Nous avons introduit un composant Wicket supplémentaire dans nos pages (le WebMarkupContainer associé au wicket:id "pageBody"). Comme celui-ci est associé à la balise <body> de la page, il devient mécaniquement le parent de tous les autres composants qu'elle contient. Or, Wicket impose une stricte correspondance entre la hiérarchie des éléments côté HTML et côté Java...
Théoriquement, nous serions donc obligés de modifier toutes nos pages afin que leurs composants (par exemple les deux liens) soit ajoutés au WebMarkupContainer au lieu de leur page !

Heureusement, Wicket propose un mécanisme permettant de déclarer un composant comme étant hiérarchiquement transparent :

  1. WebMarkupContainer container = new WebMarkupContainer("pageBody") {
  2. @Override
  3. public boolean isTransparentResolver() {
  4. return true;
  5. }
  6. };
  7. String pageId = getClass().getSimpleName();
  8. container.add(new AttributeModifier("id", true, new Model<String>(pageId)));
  9. add(container);

Grâce à cette simple astuce, la présence du WebMarkupContainer intermédiaire n'impacte plus la hiérarchie des composants Wicket, et nulle modification de code n'est nécessaire.

Conclusion

C'est maintenant l'heure du bilan.

  • Nous avons tout d'abord revu la bonne pratique de codage CSS qui consiste à identifier les pages de manière unique, afin de mieux contrôler l'affichage des éléments y apparaissant.
  • Ensuite, nous avons réfléchi à son implémentation au sein d'applications Wicket utilisant des pages templates : où placer l'identifiant et comment le générer dynamiquement.
  • Enfin, nous avons vu comment contourner élégamment les contraintes de Wicket pour ne pas impacter le code existant.

Au final, nous disposons d'un système simple, automatisé et transparent, qui peut être installé rapidement au niveau des pages templates lors du démarrage des projets.

Je vous recommande de télécharger l'archive Wicket-tips-css.zip (en annexe) et jouer un peu avec l'application.
La commande Gradle suivante permet de la compiler, déployer et lancer sur un serveur Jetty embarqué :

  1. gradle jettyRun

Elle sera alors disponible à l'adresse suivante : http://localhost:8080/Wicket-tips-css-1/.


Commentaires

1. Le mercredi 13 janvier 2010, 13:05 par Benoit Moussaud

Merci beaucoup, exactement ce que je cherchais à faire.

Ajouter un commentaire

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