7 tip

7 astuces pour créer un thème Drupal que vous n'avez peut-être jamais entendues

La création de thèmes dans Drupal n'est pas aussi facile qu'il n'y paraît. Par conséquent, il y a ici de l'espace pour faciliter votre travail. Souvent, cependant, nous prenons des raccourcis à cette fin, mais ceux-ci n'apportent pas toujours les résultats souhaités ou contredisent la philosophie de Drupal et les meilleures pratiques généralement acceptées.

Probablement, beaucoup d'entre vous savent que, comme dans tout autre CMS ou framework sur lequel nous travaillons, dans Drupal, nous pouvons atteindre les mêmes objectifs en choisissant différents chemins pour les atteindre. Au cours de mon expérience, j'ai été confronté aux erreurs courantes commises par les débutants dans Drupal à plusieurs reprises, et j'ai réussi à suivre de nombreux codes créés par des développeurs Drupal débutants et expérimentés. J'ai vu à quel point les approches qu'ils utilisent sont différentes. Dans le cadre de cet article, je voudrais présenter 7 astuces que j'ai sélectionnées (ou simplement des raccourcis) qui facilitent le travail sur la création d'un thème, dont beaucoup de programmeurs n'ont jamais entendu parler.

En raison du fait qu'à chaque nouvelle version de la sous-version de Drupal, il y a divers changements significatifs, je voudrais souligner les exemples discutés ici qui sont compatibles avec la version centrale 8.9.x. Tous les trucs présentés, bien qu'ils puissent parfois être controversés dans leur utilisation, sont pleinement conformes à l'art et peuvent être utilisés pour des projets.

1. Utilisation de la clé #context dans le tableau de rendu

L'un des problèmes courants lors de la création de modèles est la capacité de transférer des données entre eux. Cela oblige généralement les programmeurs à effectuer des opérations complexes, par exemple lors du traitement des modèles. Le défi devient encore plus difficile lorsque certains hooks ont déjà été mis en cache et que le chargement de données était encore nécessaire. Il peut y avoir de nombreuses idées pour résoudre le problème, mais avec la version Drupal 8.8, le problème est résolu par la clé #context.

Supposons que nous ayons implémenté hook_preprocess_node, dans lequel nous ajoutons un élément supplémentaire en utilisant un modèle dédié, mais nous aimerions toujours avoir accès à l'objet nœud dedans. C'est là que la clé #context est utile.

function my_module_preprocess_node(array &$variables) {
  $node = $variables['node'];

  $variables['my_custom_element'] = [
    '#theme' => 'my_custom_theme',
    '#title' => t('Mon titre de thème personnalisé'),
    '#context' => [
      'entity' => $node,
    ],
  ];
}

Cela permet de passer n'importe quelle donnée d'un tableau de rendu à un autre. Vous pouvez y faire tenir n'importe quoi, par exemple une instance d'un objet dont nous aurons besoin lors du traitement ultérieur :

function my_module_preprocess_my_custom_theme(array &$variables) {
  $node = $variables['#context']['entity'];
  // Faites ce dont vous aviez besoin avec l'entité nœud dans votre thème personnalisé...
}

Il convient de mentionner que cette variable est également disponible dans hook_theme_suggestion_alter, ce qui peut encore faciliter la formulation de suggestions pour des noms de modèles supplémentaires dépendant du contexte.

2. Support du cache dans les modèles twig

Les designs graphiques de plus en plus exigeants des sites Web, ainsi que l'optimisation des pages (en termes de quantité de code HTML retourné), nous obligent à utiliser certaines solutions de raccourcis, par exemple en extrayant directement la valeur du champ dans le modèle d'entité (type de nœud).

Les solutions telles que

{{ content.field_my_reference_field.0 }}

l'affichage d'un seul champ au lieu de la variable de contenu peuvent avoir des conséquences désastreuses sous forme de problèmes de cache. Dans les tableaux de rendu, outre le code HTML final, il y a aussi des clés responsables de la conservation de ces métadonnées de cache : balise, contexte et max-age. Si vous voulez être sûr que les métadonnées seront correctement collectées par le service renderer, il suffit d'ajouter une ligne magique dans le modèle :

{% set rendered = content|render %}

Dans l'exemple d'un modèle de nœud, ce sera la variable de contenu, mais cela peut être n'importe quelle variable "principale" de votre modèle. Passer un tel tableau de rendu principal à travers les filtres de rendu vous permet de collecter correctement toutes les métadonnées de cache, sans avoir à afficher toute la variable de contenu. Dans le même temps, la solution n'a aucun impact négatif sur les performances de l'exécution du code, tant que nous utilisons les mécanismes de cache.

3. Suggestion de thème sans utiliser hook_theme_suggestion_

Oui, cela ressemble à quelque chose de complètement fou. Cependant, cette solution est disponible "tout de suite". Il suffit d'ajouter la suggestion aux valeurs cachées dans #theme ou theme_hook_original dans n'importe quel tableau de rendu (par exemple dans hook_preprocess_HOOK) en respectant la convention de nommage des modèles.

Par conséquent, la valeur de la variable définie de cette manière

function my_module_preprocess_paragraph(array &$variables) {
  $paragraph = $variables['paragraph'];

  if ($paragraph->bundle() === 'my_awesome_bundle') {
    $variables['theme_hook_original'] = 'paragraph__my_custom_theme_suggestion';
  }
}

enregistre une suggestion pour un modèle de base donné (paragraphe), vous permettant de créer un fichier de modèle nommé : paragraph-my-custom-theme-suggestion.html.twig Néanmoins, je limiterais cette solution à des cas exceptionnels. Pour une utilisation générale, je recommanderais un hook dédié à cet effet.

4. Remplacement de filtre de sécurité | raw

Ce filtre est généralement considéré comme potentiellement dangereux (augmente la possibilité d'attaques XSS réussies) et est utilisé dans des cas isolés. Mais il doit être évité autant que possible. Je suggère de chercher un meilleur moyen, d'autant plus que l'un d'entre eux est extrêmement facile à utiliser, comme vous le verrez dans un exemple.

Supposons que nous ayons un champ qui doit finalement afficher l'image, mais pour une raison quelconque, nous aimerions omettre tout le code HTML généré en cours de route.

La façon la plus simple de procéder est la suivante :

{{ content.field_my_image_field|render|striptags('<img>')|trim }}

Les développeurs Drupal expérimentés savent qu'une telle solution, au lieu d'afficher une image sur la page, affichera l'ensemble comme du texte brut encodé en entités HTML. Oui, vous pouvez ajouter un filtre raw, mais vous pouvez le faire différemment en utilisant l'API de rendu de Drupal avec la clé #markup :

{{ {'#markup': content.field_my_image_field|render|striptags('<img>')|trim} }}

La méthode est peut-être un peu moins lisible qu'avec le filtre, mais elle est sûre. Le code HTML sera toujours filtré ici et seuls les balises sûres autorisées seront transmises.

5. Référence à un fichier modèle d'un autre thème/module

Si vous n'avez pas utilisé les mécanismes de Twig tels que include, embed ou extend dans vos fichiers modèles, je suggère de prendre la règle DRY en compte dans tous les aspects de la programmation (ou du développement Web en général). Les thèmes sont les endroits qui souffrent le plus du code dupliqué, à la fois dans les fichiers modèles et dans les feuilles de style.

J'ai souvent rencontré l'ensemble du fichier de modèle qui a été copié, par exemple d'un thème hérité ou d'un module, uniquement pour ajouter quelques balises HTML environnantes ou modifier un bloc de modèle unique. Le même effet peut être obtenu en faisant référence à un fichier parent en utilisant les alias @module_name @theme_name. Supposons que nous ayons un fichier de modèle (card) dans notre module d'exemple nommé custom_card

<div class="card">
  {% block card_header %}
    <div class="card__header">{{ header }}</div>
  {% endblock %}
  
  {% block card_content %}
    <div class="card__content">{{ content }}</div>  
  {% endblock %}
  
  {% block card__footer %}
    <div class="card__footer">{{ footer }}</div>
  {% endblock %}
</div>

Et maintenant dans notre thème, nous aurions besoin d'ajouter des éléments supplémentaires dans le bloc header, par exemple une icône. La chose la plus courante est de copier tout le code du modèle, mais cela peut être fait plus simplement et sans répéter le code :

{% extends '@custom_card/card.html.twig' %}

{% block header %}
    <div class="card__header">
      <div class="icon">{{ icon }}</div>
      {{ header }}
    </div>
{% endblock %}

6. Rechargement de vue à l'aide d'InvokeCommand

Comme vous le savez, le module Views permet de créer des vues qui utilisent la technologie AJAX. Cela est fait pour basculer dynamiquement entre les différentes pages de la vue, mais aussi pour afficher les résultats de la réponse aux changements de valeur du formulaire fourni. Mais que faire lorsque le contenu de la vue doit être actualisé en raison de l'exécution d'une action en dehors de celle-ci, par exemple après l'envoi d'une demande au contrôleur d'un autre bloc ? Ou pour soumettre un formulaire sur la même page ? Eh bien, il existe deux fonctionnalités très pratiques disponibles dans le noyau de Drupal.

La première est l'événement par défaut RefreshView enregistré sur la vue, qui provoque l'actualisation de la vue à l'aide d'AJAX (je vous recommande de le saisir dans la console du navigateur sur votre vue d'exemple et d'observer l'effet).

Le deuxième mécanisme est InvokeCommand, qui permet dans la réponse AJAX de retourner l'appel de la méthode jQuery, avec les paramètres donnés sur l'élément DOM. Combinés, cela vous donne cet exemple de code :

class MyCustomController extends ControllerBase {

  /**
   * Effectuer la mise à jour et actualiser la vue.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse
   *   Réponse Ajax.
   */
  public function doUpdateAndRefresh(): AjaxResponse {
    // Effectuer une sorte de logique nécessaire pour votre contrôleur.
    $this->doUpdateOperations();

    // Actualiser la vue.
    $response = new AjaxResponse();
    $response->addCommand(new InvokeCommand('#my-view-block', 'trigger', ['RefreshView']));

    return $response;
  }

}

Dans mon exemple, il y avait des formulaires pour l'approbation des utilisateurs à l'intérieur de la vue générée. En réponse à l'entrée d'un tel formulaire, j'avais besoin d'afficher une liste mise à jour. Comme vous pouvez le voir, fondamentalement une ligne supplémentaire a fait toute la magie sans aucun effort supplémentaire.

7. Utilisation de Drupal.debounce

Pour finir, je veux décrire quelque chose qui n'est pas tant un truc qu'une bonne pratique. La bibliothèque Underscore.js est incluse dans le noyau de Drupal, mais de nombreux développeurs ne l'utilisent pas du tout. Elle comprend un certain nombre de fonctionnalités. L'un des cas les plus courants où les capacités de cette bibliothèque ne sont pas utilisées est le soi-disant déboucement, c'est-à-dire la prévention de l'exécution multiple du code en réponse à un événement donné - le plus souvent c'est un changement de largeur de la fenêtre du navigateur.

J'ai vu souvent du code qui ressemblait à :

function onResize(callback) {
  var timer;
  return function (event) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(callback,100, event);
  };
}

$(window).resize(onResize(function(e) {
  // L'implémentation de redimensionnement va ici.
}));

Bien sûr, il s'agit d'une implémentation exemplaire et de la plus simple, et ce code s'exécutera correctement. La question est : pourquoi réinventer la roue ? Réponse : pour améliorer la compatibilité de Drupal dans le noyau de Drupal, et aussi pour augmenter la commodité du travail pour les développeurs - il existe un port de débouceur de la bibliothèque underscore.

Ajoutez simplement une dépendance à votre bibliothèque

- core/drupal.debounce

et ajoutez l'implémentation suivante dans le code JS :

$(window).resize(Drupal.debounce(function () {
  // L'implémentation de redimensionnement va ici.
}, 100));

N'est-ce pas plus facile ? Et surtout, nous utilisons quelque chose qui a déjà été bien écrit une fois.

Résumé

Bien que j'aie rencontré un grand nombre d'approches intéressantes pour travailler avec des thèmes dans notre agence Drupal, il faut souligner que beaucoup de choses viennent avec l'expérience. En particulier, les développeurs débutants devraient suivre la philosophie de Drupal dès le début. Ils devraient comprendre pourquoi ils font quelque chose d'une certaine manière. Cela nous permet de voir plus facilement les capacités de Drupal et d'apprendre à les utiliser pour augmenter le confort et l'efficacité du travail, sans perdre la propreté du code.

 

3. Best practices for software development teams