Agencement comme Pinterest

Organiser les éléments d’un méga-menu à la façon Pinterest sur Drupal 7

Parmi les nombreux concepts ergonomiques répandus sur le web, un en particulier a récemment attiré notre regard comme celui de tant d’autres. Vous connaissez surement ce mode de présentation en grille rendu très populaire par Pinterest, le réseau social « à la mode ». Cette tendance appelée dynamic grid layout en anglais ne possède d’ailleurs pas encore de nom propre autre que « à la façon Pinterest », c’est du moins la meilleure manière d’obtenir des résultats concrets sur Google à l’heure actuelle.

Son succès est tel que plus d’une librairie en proposent son implémentation. Parmi celles existantes, Masonry ou encore Freetile.js sont les plus connues.

Ces plugins introduisent une méthode dynamique de placement dans l’espace qu’il n’est pas possible de reproduire avec du CSS. Appréciez la différence :

Différences avant et après Masonry

Ces plugins nous ont rendu service par le passé afin de réorganiser les éléments d’un mega-menu présent sur le site de l’INHESJ, un des projets de Canal-Web. Comme le montre l’illustration ci-dessus, lorsque le nombre de liens présents dans un sous-menu diffère trop de ses voisins, de grands espaces vides apparaissent, dégradant la mise en page.

Voici ce que ce tutoriel va vous permettre de faire :
Démonstration
Passez votre souris sur le lien « Mega Menu » pour voir un exemple ; )

Il est simple d’implémenter un de ces scripts sur un site hors du contexte d’un CMS mais l’implémentation sur Drupal 7 présente quelques subtilités, Canal-Web est là pour vous éclairer !

Quelle librairie ?

Premièrement, il faut choisir un plugin. Pour ce que nous voulons faire, cela n’a pas grande importance. Les personnes souhaitant construire un projet totalement basé sur ce type d’interface gagneraient à s’intéresser aux différents avantages de chaque partie, mais notre besoin étant très basique, nous poursuivrons ce tutoriel avec Masonry. Cette librairie étant sensiblement plus légère que Freetile.js.

Modules nécessaires

Pour pouvoir commencer à travailler sur votre menu, il vous faut un module : Menu Block.

Pourquoi ? Une fois un menu créé sur Drupal, un bloc est automatiquement généré. Hélas, son niveau de configuration est proche du néant et sans ce module il vous sera impossible d’afficher une menu complet avec liens enfants sur plus de deux niveaux. C’est un bug qui n’a pas encore été corrigé et après la sortie de Menu Block, il est probable qu’aucun correctif ne sera mis en ligne. La sortie de Drupal 8 réglera surement tout ça.

Au passage, si vous souhaitez réaliser un menu à l’aide d’une taxonomie, jetez un coup d’œil sur le module Taxonomy Menu, cela devrait vous intéresser ;).

Création et mise en place du menu

Une fois Menu Block installé, rendez-vous sur la page « blocs » (admin/structure/block). Créez un nouveau bloc en cliquant le bouton « Ajouter un bloc de menu » et renseignez le formulaire de configuration comme indiqué ci-dessous :

  • Laissez vide le titre du bloc
  • Cliquez sur « Options avancées »
  • Dans la liste déroulante « Menu », sélectionnez celui qui vous intéresse
  • Cochez la case « Déplier tous les enfants de cette arborescence »
  • Dans « Paramètres de la région », selectionnez la région (du thème que vous utilisez) dans laquelle vous voulez placer votre menu
  • Sauvegardez le bloc

La structure HTML

Le rendu HTML des menus sur Drupal est constitué par défaut de balises <ul> et <li>, ce qui convient à Masonry lors d’une utilisation basique. Mais tout cela finira tôt ou tard par devenir bloquant si votre interface devient plus complexe. Rendons la structure de ce menu un peu plus flexible, voulez-vous ?

Comment faire ?

Afin de réaliser nos modifications, nous allons avoir besoin d’un endroit où placer un peu de code, le fichier template.php de votre thème me semble être l’endroit rêvé. Ce fichier se trouve généralement à la racine du dossier de votre thème. Par exemple : sites/all/themes/montheme. D’ailleurs, le nom du thème d’exemple sera… mon_theme ! Pour la suite de l’article, pensez à bien mettre à jour le nom de vos fonctions en rapport au nom de votre propre thème.

Code, code, code !

Menu_block propose une grande variété de Hooks permettant la réécriture de templates (disponibles dans le README.txt du module). Nous allons en utiliser trois :

  • [theme]_menu_tree__menu_block() qui s’occupera de surcharger le thème par défaut  theme_menu_tree(), le thème s’occupant d’envelopper tous les <li> dans un <ul>
  • [theme]_menu_tree__depth_[depth]() se chargera de la même chose que le hook précédent, seulement à la profondeur désirée
  • [theme]_menu_link__menu_block() qui  lui surchargera theme_menu_link(), le rendu HTML d’un lien dans un <li>

Commençons par le plus simple :

<br />
function mon_theme_menu_tree__menu_block($variables) {<br />
  // Remplace les balises &lt;ul&gt; par des &lt;div&gt;.<br />
  return '&lt;div&gt;' . $variables['tree'] . '&lt;/div&gt;';<br />
}</p>
<p>function mon_theme_menu_tree__depth_2($variables) {<br />
  // Remplace les balises &lt;ul&gt; par des &lt;div&gt; pour les liens de second niveau. Rajoute au passage une<br />
  // profondeur de div pour permettre à Masonry de facilement repérer les liens sur lesquels agir.<br />
  return '&lt;div class=&quot;menu columns&quot;&gt;&lt;div class=&quot;inner-columns&quot;&gt;' . $variables['tree'] . '&lt;/div&gt;&lt;/div&gt;';<br />
}<br />

Facile, il suffit de récupérer le code PHP de theme_menu_tree() pour ensuite remplacer le rendu HTML par ce que nous voulions.

Ensuite, la même chose pour theme_menu_link(). Récupération du code source et modifications manuelles :

<br />
function mon_theme_menu_link__menu_block($variables) {<br />
  $element = $variables['element'];<br />
  $sub_menu = '';</p>
<p>  // Récupération de la profondeur du lien.<br />
  $depth = $variables['element']['#original_link']['depth'];</p>
<p>  // Ajoute quelques classes par rapport à la profondeur du lien.<br />
  switch ($variables['element']['#original_link']['depth']) {<br />
    case 1:<br />
      // Ajout d'une classe sur le conteneur du lien.<br />
      $element['#attributes']['class'][] = 'top-links';</p>
<p>      // Ajout d'une classe sur le lien.<br />
      $element['#localized_options']['attributes']['class'][] = 'top-links-link';</p>
<p>      if ($element['#below']) {<br />
        $element['#below']['#theme_wrappers'][0] = array('menu_tree__depth_2');<br />
      }</p>
<p>      break;</p>
<p>    case 2:<br />
      // Ajout d'une classe sur les liens de 2ème niveau afin de pouvoir les repérer plus tard avec Masonry.<br />
      $element['#attributes']['class'][] = 'column';<br />
      break;<br />
  }</p>
<p>  // Précise la profondeur du lien via une classe sur le conteneur et le lien lui-même.<br />
  $element['#attributes']['class'][] = 'depth_' . $depth;<br />
  $element['#localized_options']['attributes']['class'][] = 'link_depth_' . $depth;</p>
<p>  // Le code ci-dessous est déjà présent dans theme_menu_link().<br />
  if ($element['#below']) {<br />
    $sub_menu = drupal_render($element['#below']);<br />
  }<br />
  $output = l($element['#title'], $element['#href'], $element['#localized_options']);<br />
  return '&lt;div' . drupal_attributes($element['#attributes']) . '&gt;' . $output . $sub_menu . &quot;&lt;/div&gt;\n&quot;;<br />
}<br />

Que vous soyez expérimenté ou non ne devrait pas changer grand chose, contentez-vous de copier/coller les trois fonctions ci-dessus (en changeant leur nom par rapport à votre thème) et tout devrait se mettre en place sans efforts. Le code est commenté afin de rester compréhensible 😉

N’oubliez pas de vider le cache de Drupal pour voir apparaître les modifications.

Ajout du CSS

Nous n’allons pas nous étendre sur le sujet. Chaque thème est unique de par sa structure et il n’est pas possible de donner un guide précis sur l’application du CSS. Vous êtes libre de pomper au maximum la feuille CSS présente sur la page de démonstration !

Pour ceux qui n’auraient jamais eu le plaisir de construire un menu déroulant en CSS, les tutoriaux ne manquent pas.

Ajout du JavaScript

C’est l’heure d’ajouter la poudre magique qui va nous faire passer de quelque chose comme ça :

Aperçu du menu avant l'application de Masonry

à quelque chose de plus … dans ce genre là :

Aperçu du menu après l'application de Masonry

Il va nous falloir ajouter du javascript. À chacun sa méthode. Vous pourriez l’ajouter directement dans le template de votre thème à l’aide de page.tpl.php par exemple. Chez Canal-Web, on préfère rester constant ! Nous allons donc rester dans le contexte de template.php et utiliser un petit Hook :

<br />
function mon_theme_preprocess_page(&amp;$variables) {<br />
  // Utilisation de drupal_add_js pour insérer du JavaScript directement dans le footer.<br />
  // Le script ci-dessous va détecter tous les sous-menus de second niveau et va initialiser<br />
  // Masonry pour chacun d'eux.<br />
  drupal_add_js(&quot;<br />
    (function ($) {<br />
      $('.menu-block-ID_DU_MENUBLOCK .columns .inner-columns').each(function(i){<br />
        $(this).masonry({<br />
          itemSelector: '.column'<br />
        });<br />
      });<br />
    })(jQuery);&quot;<br />
  , array('type' =&gt; 'inline', 'scope' =&gt; 'footer'));<br />
}<br />

Comme précisé dans les commentaires du code, ce script va scanner la page pour y trouver tous les sous-menus de second niveau et leur appliquer les vertus bienfaisantes de Masonry. N’oubliez pas de modifier « ID_DU_MENUBLOCK » par la valeur présente dans votre propre code source (vous pouvez également trouver l’ID de votre block de menu via l’URL de son formulaire de configuration). Le cache de Drupal doit également être vidé.

C’est fait ! À vous de régler les détails graphiques ; )

Problèmes récurrents

Si une fois le tutoriel appliqué, vous n’arrivez toujours pas à faire fonctionner votre menu, voici quelques problèmes sur lesquels nous avons trébuché par le passé.

Les liens sont entassés les uns sur les autres

Si Drupal tourne avec une version de jQuery antérieure à 1.4.4, la détection des tailles des éléments en display:none est mal calculée. Pour résoudre ce problème, plusieurs solutions :

Première méthode

Vu que display:none fonctionne mal, il est possible de remplacer le CSS des éléments cachés avec un visibility:hidden/visible. Il faut ensuite penser à jouer avec le z-index de la div entourant le menu et de #main afin de s’assurer que le menu, lorsqu’il est caché, ne passe pas par dessus le contenu principal, empêchant la souris d’interagir avec certains éléments.

Voici un exemple très simplifié de la solution :

<br />
.menu {<br />
  position: relative;<br />
  visibility: hidden;<br />
  z-index: 1;<br />
}<br />
.menu:hover {<br />
  visibility: hidden;<br />
  z-index: 3;<br />
}<br />
#main {<br />
  position: relative;<br />
  z-index: 2;<br />
}<br />

Ne pas oublier la position:relative pour que le z-index puisse avoir un effet ; )

Deuxième méthode

Si Masonry ne peut détecter la taille des éléments en display:none, et bien retirons ce style avant de démarrer Masonry !

Plus précisément, il suffit d’afficher brièvement les éléments avant de lancer Masonry et de les cacher aussitôt. Le menu n’aura pas le temps d’apparaître mais ce sera suffisant pour récupérer les tailles. Pour ce faire, il suffit de changer légèrement le script de tout à l’heure :

<br />
(function ($) {<br />
  $('.menu-block-ID_DU_MENUBLOCK .columns .inner-columns').each(function(i){<br />
    // Affiche le menu afin que Masonry puisse calculer la taille des éléments.<br />
    $(this).parent().show();<br />
    $(this).masonry({<br />
      itemSelector: '.column'<br />
    });<br />
    // Masonry à fini son travail, on cache les éléments.<br />
    $(this).parent().css('display', '');<br />
  });<br />
})(jQuery);<br />

Conclusion

Hop là, ça c’est fait. Un problème rencontré ? Des corrections à apporter ? D’autres méthodes à employer ? N’hésitez pas à donner votre avis !

Notez cet article

A propos de Peeter Rannou

Peeter est un développeur passionné employé chez Canal-Web depuis maintenant plus de 4 ans. Très actif dans le monde de l'Open Source, il contribue à de nombreux projets sur le web. Suivez son activité sur Twitter via le compte @thedotwriter.

Laissez un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>