Manipuler les menus de Drupal 8 (le retour)

Vue en contre-plongée d'un arbre et de ses branches

Dans un précédent billet, nous avions pu voir que l'API Drupal 8 pour manipuler les menus avait assez peu changé, avec l'utilisation de fonction de thème ou encore de preprocess. Mais c'était encore avec une version alpha, et les choses ont quelque peu évolué depuis...et notamment la sortie de la première version bêta de Drupal 8.

Afin de permettre aux développeurs front-end de modifier les classes sans avoir besoin à recourir à du développement back-end, les classes sont désormais gérées directement depuis les templates Twig. Même s'il restera possible d'ajouter des classes depuis des fonctions de preprocess. Il en est autrement des fonctions de thème appliquées aux menus (THEME_menu_tree_HOOK ou THEME_menu_link_HOOK) qui ont, semble-t-il, bel et bien disparu.

J'ai pu le découvrir lors de la migration de mon premier site Drupal 8 (en version alpha) vers la version bêta 1 sortie fin septembre 2014. Migration qui ne s'est pas faite sans douleur...mais c'était logique et assumé ;-)

Pour pouvoir modifier les classes d'un menu sous Drupal 8, il nous suffit désormais de surcharger le template menu.html.twig (utilisé pour tous les menus d'un site Drupal 8, que vous pourrez trouver dans le répertoire core/modules/system/templates) et nous bénéficions alors de toute la puissance de Twig pour ajouter une logique spécifique sans avoir besoin de disséminer du code PHP partout. D'ailleurs Twig interdira désormais d'utiliser quelque code PHP que ce soit dans les templates. Très bonne chose... ceci nous évitera de trouver par exemple, au détour d'une reprise de site en maintenance, des update de la base de données au niveau des templates du thème.

Pour pouvoir connaître les suggestions possibles des surcharges des templates, Twig dispose d'un debuggueur natif qu'il suffit d'activer depuis le fichier services.yml (situé sous le répertoire sites/default). En activant le debug, l'auto_reload et en désactivant le cache de twig nous allons pouvoir accéder à de précieuses informations directement depuis le markup des pages. Pensez surtout à désactiver ces options et à réactiver le cache avant la mise en production du site.


/* sites/default/services.yml */

parameters:
  twig.config:
    debug: true
    auto_reload: true
    cache: false

 

Avec ces options activées nous pouvons consulter le template utilisé et les possibles surcharges depuis les outils de développement d'un navigateur Internet. L'image ci-dessous illustre ce principe avec le menu principal dont le nom machine est main. Les templates possibles sont donc menu.html.twig ou pour cibler précisement ce menu menu--main.html.twig.

Twig debug in markup

En copiant ce fichier dans notre thème et en le renommant nous allons pouvoir ajouter à ce menu des classes spécifiques. Dans l'exemple ci-dessous nous allons rajouter à la balise ul de premier niveau uniquement, grâce à la condition {% if menu_level == 0 %}, les classes menu main-menu et slimmenu et l'attribut id="navigation". A noter l'utilisation de la variable attributes associée à la méthode .addClass qui permet d'ajouter des nouvelles classes à celles qui seraient déjà présentes dans la variable attributes.


 {#
/**
 * @file
 * Default theme implementation to display a menu.
 *
 * Available variables:
 * - menu_name: The machine name of the menu.
 * - items: A nested list of menu items. Each menu item contains:
 *   - attributes: HTML attributes for the menu item.
 *   - below: The menu item child items.
 *   - title: The menu link title.
 *   - url: The menu link url, instance of \Drupal\Core\Url
 *   - localized_options: Menu link localized options.
 *
 * @ingroup themeable
 */
#}
{% import _self as menus %}

{#
  We call a macro which calls itself to render the full tree.
  @see http://twig.sensiolabs.org/doc/tags/macro.html
#}
{{ menus.menu_links(items, attributes, 0) }}

{% macro menu_links(items, attributes, menu_level) %}
  {% import _self as menus %}
  {% if items %}
    {% if menu_level == 0 %}
      <ul{{ attributes.addClass('menu main-menu slimmenu') }} id="navigation">
    {% else %}
      <ul class="menu">
    {% endif %}
      {% for index, item in items %}
      {% set id=index|split(':')|last %}
        <li {{ item.attributes.addClass(id|clean_class) }} >
          {{ link(item.title, item.url) }}
          {% if item.below %}
            {{ menus.menu_links(item.below, attributes, menu_level + 1) }}
          {% endif %}
        </li>
      {% endfor %}
    </ul>
  {% endif %}
{% endmacro %}

 

Dans cet exemple, nous ajoutons également à chaque balise li une classe unique qui sera basée sur l'identifiant unique du menu. Pour ce faire, nous avons modifié la boucle du template fournie {% for item in items %} par {% for index, item in items %} afin de récupérer l'identifiant unique de chaque entrée de menu (utilisé comme clé dans le tableau items). En retravaillant légèrement cet index avec les fonctions de filtre de Twig ({% set id=index|split(':')|last %}) pour récupérer un identifiant plus homogène, nous pouvons alors ajouter un identifiant unique et constant à chaque balise li du menu principal, après l'avoir formatté avec le filtre clean_class.

Afin de procéder au debug directement depuis les templates, Twig fournit une fonction dump() qui permet d'ausculter le contenu des variables. Il suffit de rajouter la ligne suivante dans votre template pour pouvoir consulter la valeur d'une variable.


{{  dump(variable) }}

 

Le module Devel propose également une fonction kint (au travers de son sous-module Devel Kint) qui permet de disposer d'un tableau de valeurs plus facilement exploitable.


{{  kint() }}
{{  kint(variable) }}

 

A noter que le module Devel peut ne pas être encore complètement fonctionnel à la date de rédaction de ce billet. Et que des évolutions peuvent encore surgir au fur et à mesure des versions bêta qui seront publiées, même si le principe de porter la logique du theming vers le système de template Twig est quant à elle bien enracinée.

J'espère que ces quelques lignes vous auront fait gagner quelques heures pour vos prochains projets Drupal 8. N'hésitez pas surtout à compléter ce billet dans les commentaires, il y a très certainement encore quelques points qui méritent des précisions. Et il y aura encore très certainement des évolutions jusqu'à la sortie de la version finale de Drupal 8.

 

Commentaires

Soumis par pitav (non vérifié) le 13/03/2015 à 15:26 - Permalien

Merci pour l'info.
Je viens de me lancer sur D8 pour un site client et bien sûr c'est la pêche à la doc.
En l’occurrence pour le menu principal du site, j'utilise le template menu--main.html.twig très légèrement customisé couplé au script superfish.js et... ça fonctionne nickel sans aucun module !

Soumis par Sébastien (non vérifié) le 04/10/2017 à 23:10 - Permalien

Merci. Ça fonctionne très bien. Il n’y aurait pas un autre moyen d’identifier l’élément de menu avec autre chose que l'identifiant unique ? On obtient des ids comme _a0b7881-7690-4240-bb1b-2fe7ef89a7eb. Ce n’est pas gênant mais pas très facile à lire dans le code source.

Ajouter un commentaire