Image mise en avant pour l'article

Améliorer l’autocomplétion de Drupal pour une meilleure expérience utilisateur

20 mai 2025
Drupal
L’autocomplétion de Drupal est une fonctionnalité très répandue pour les champs de contenu mais qui a parfois ses limites à cause du caractères succinct des données proposées en retour. Découvrons comment améliorer cela.


Drupal propose nativement une fonctionnalité d’autocomplétion que l’on appelle autocomplete. L’utilisateur commence à taper du texte dans un champ et cette fonctionnalité suggère une liste de résultats qui correspondent au texte renseigné.

Cette fonctionnalité est très utile dans le processus éditorial pour pouvoir facilement faire référence à d’autres contenus du site depuis le contenu que l’on édite.

Toutefois, au fur et à mesure que le contenu de votre site s’étoffe, une difficulté peut apparaître : comment différencier des contenus qui ont le même titre pour savoir lequel référencer dans notre contenu éditorial ?

Nous vous proposons ici une solution pour améliorer l’affichage des données suggérées par l’autocomplete de Drupal et ainsi faciliter la vie de vos contributeurs.

Un développeur développe un site Internet sur son ordinateur

Utilisation de l’autocomplete de Drupal dans sa version native

Commençons par présenter la fonctionnalité d’autocomplete telle qu’elle est proposée par Drupal, puis mettons en lumière la problématique rapidement rencontrée.

À quoi ça sert l’autocomplete ?

Comme nous l’avons dit en introduction, cette fonctionnalité est très utile pour (et très utilisée par les contributeurs) faire référence à d’autres contenus du site depuis un contenu source.

Imaginons par exemple un article de blog « article A » qui traite de la thématique « Drupal ». Il serait intéressant, sur la page de cet article, de suggérer à nos lecteurs d’autres articles, choisis arbitrairement, qui traitent eux aussi de la thématique « Drupal ».

Pour cela nous allons, dans l’édition de notre « article A » , faire référence à des articles « article B » et « article C ». Facile, jusqu’ici. Oui, mais ce que Drupal attend comme donnée dans notre champ de référencement, c’est l’identifiant interne unique associé à chaque contenu.

Si nous avons :

  • article A : identifiant (ID) 123
  • article B : (ID) 456
  • article C : (ID) 789

Il faut donc que dans la page d’édition de notre « article A », nous renseignions les valeurs 456 et 789 pour que Drupal comprenne qu’il s’agit des contenus « article B » et « article C ».

On imagine difficilement des contributeurs aller rechercher ces valeurs numériques. Ce serait bien plus simple de renseigner le titre du contenu souhaité et que Drupal sache à quel identifiant de contenu nous faisons référence.

Bingo, c’est justement à ça que sert l’autocomplete !

Nos contributeurs n’ont qu’à renseigner du texte dans le champ de référencement et attendre que Drupal leur propose une liste de suggestions de titres de contenus.

Autocomplétion de Drupal avec un exemple de suggestion de solution

Il n’y a plus qu’à cliquer sur la bonne suggestion pour qu’elle soit ajoutée au champ. Et Drupal de gérer tout seul l’association de l’identifiant unique à notre contenu.

Autocomplétion de Drupal avec la sélection par défaut

Les options proposées par défaut par Drupal pour cette fonctionnalité permettent de jouer :

  • sur le nombre de suggestions proposées,
  • et sur le critère de recherche : à savoir, est-ce que la suggestion doit commencer par le texte renseigné ou bien est-ce que la suggestion doit contenir le texte renseigné ?

Les limites de l’autocomplete natif de Drupal

À la lecture du paragraphe précédent, nous pouvons nous dire que nos problèmes sont résolus.

Mais un problème peut vite se poser avec l’augmentation du nombre de contenus sur notre site : Comment faire pour différencier deux contenus qui auraient le même titre ? Ou bien comment savoir si une suggestion est un article ou une page ?

Autocomplétion de Drupal avec des titres similaires

 

Amélioration de l’expérience utilisateur de l’autocomplete de Drupal

Maintenant que le problème est posé, voyons la solution. Pour facilement différencier nos suggestions, nous souhaiterions que les résultats retournés par l’autocomplete de Drupal nous donnent des informations supplémentaires telles que :

  • le type de contenu,
  • l’identifiant du contenu,
  • la date de création et/ou modification,
  • l’auteur du contenu.

Création de notre autocomplete amélioré

Dans les grandes lignes, voici ce que nous allons faire pour arriver à nos fins :

  • créer un module personnalisé,
  • surcharger le service entity.autocomplete_matcher de Drupal,
  • créer une nouvelle classe utilisée par ce service et qui hérite de la classe EntityAutocompleteMatcher,
  • utiliser notre nouvelle classe pour récupérer les données souhaitées,
  • (optionnel) ajouter un peu de mise en forme via du code CSS pour une meilleure lisibilité.

Commençons par créer un nouveau module que nous appelons custom_autocomplete.

> Redéclaration du service d’autocomplétion

Déclarons ensuite le service suivant dans le fichier custom_autocomplete.services.yml :

services:
 entity.autocomplete_matcher:
  class: Drupal\custom_autocomplete\Entity\CustomEntityAutocompleteMatcher
  arguments: [
   '@plugin.manager.entity_reference_selection',
   '@entity_type.manager',
   '@entity_type.bundle.info',
   '@date.formatter',
  ]

Le service que nous déclarons s’appelle entity.autocomplete_matcher. Ce service est également défini dans le core de Drupal dans le fichier core.services.yml. Nous redéclarons donc le service.

Le fait de déclarer à nouveau le même nom de service est ce qui va nous permettre de surcharger l’autocomplete de Drupal. En effet, lorsque Drupal va vouloir utiliser le service, il va d’abord balayer les fichiers du core pour y trouver la déclaration du service, puis il va ensuite balayer les fichiers des modules et il va y retrouver une nouvelle déclaration de ce même service. C’est donc cette dernière déclaration qui fait foi.

> Création de la classe d’autocomplete

Créons maintenant la classe associée à la déclaration de notre service dans le fichier custom_autocomplete/src/Entity/CustomEntityAutocompleteMatcher.php.

namespace Drupal\custom_autocomplete\Entity;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Tags;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityAutocompleteMatcher;
use Drupal\Core\Entity\EntityAutocompleteMatcherInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
* Matcher class to get autocompletion results for entity reference.
*/
class CustomEntityAutocompleteMatcher extends EntityAutocompleteMatcher implements EntityAutocompleteMatcherInterface {

 /**
 * Constructs an EntityAutocompleteMatcher object.
 *
 * @param \Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginManagerInterface $selection_manager
 * The entity reference selection handler plugin manager.
 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
 * The entity type manager service.
 * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundleInfo
 * The entity type bundle info service.
 * @param \Drupal\Core\Datetime\DateFormatterInterface $dateFormatter
 * The date formatter service.
 */
 public function __construct(
  SelectionPluginManagerInterface $selection_manager,
  protected EntityTypeManagerInterface $entityTypeManager,
  protected EntityTypeBundleInfoInterface $bundleInfo,
  protected DateFormatterInterface $dateFormatter,
 ) {
  parent::__construct($selection_manager);
 }

}

Notre classe hérite de la classe EntityAutocompleteMatcher qui est la classe associée à l’autocomplete par défaut de Drupal. Cela nous permettra de bénéficier de ses méthodes sans avoir à les réécrire.

>> Récupération des données : la méthode getMatches()

Nous allons maintenant, dans notre propre classe, surcharger la méthode getMatches() de la classe EntityAutocompleteMatcher et qui permet de chercher les suggestions et de les renvoyer à l’utilisateur.

 /**
  * {@inheritdoc}
  */
  public function getMatches($target_type, $selection_handler, $selection_settings, $string = '') {

  // BLOC 1
  $options = $selection_settings + [
   'target_type' => $target_type,
   'handler' => $selection_handler,
  ];

  // Définit le tri par ordre alphabétique plutôt que par ID.
  if ($target_type == "node") {
   $options['sort'] = [
    'field' => 'title',
    'direction' => 'ASC',
   ];
  }
  elseif ($target_type == "taxonomy_term") {
   $options['sort'] = [
    'field' => 'name',
    'direction' => 'ASC',
   ];
  }

  $handler = $this->selectionManager->getInstance($options);


  // BLOC 2
  if (isset($string)) {
   // Récupère un tableau des ID et titres d’entités qui satisfont à la recherche.
   $match_operator = !empty($selection_settings['match_operator']) ? $selection_settings['match_operator'] : 'CONTAINS';
   $match_limit = isset($selection_settings['match_limit']) ? (int) $selection_settings['match_limit'] : 10;
   $entity_labels = $handler->getReferenceableEntities($string, $match_operator, $match_limit);


  // BLOC 3
  // Balaie les résultats et les convertit en rendu pour l’autocomplete.
  foreach ($entity_labels as $values) {
   foreach ($values as $entity_id => $label) {
    $entity = $this->entityTypeManager->getStorage($target_type)->load($entity_id);
    // Si l'entité n'existe pas, on passe à la suivante.
    if (!$entity) {
     continue;
    }


   // BLOC 4
   // Récupération de la "vraie" valeur du champ (similaire autocomplete par défaut).
   $key = "$label ($entity_id)";
   // Strip things like starting/trailing white spaces, line breaks and tags.
   $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(Html::decodeEntities(strip_tags($key)))));
   $key = Tags::encode($key);


     // BLOC 5
     // Récupération d'informations supplémentaires pour l'affichage dans l'autocomplete.
     $bundles = $this->bundleInfo->getBundleInfo($target_type);
     $bundle_label = $bundles[$entity->bundle()]['label'] ?? $entity->bundle();
     $bundle_label = $bundle_label instanceof TranslatableMarkup ? $bundle_label->render() : $bundle_label;
     // Formatage de l'affichage.
     $output = $this->getFormattedOutput($entity, $bundle_label);


     // BLOC 6
     $matches[] = ['value' => $key, 'label' => $output];
    }
   }
  }

  return $matches;
 }

Voyons en détail ce que fait cette méthode getMatches() :

  • BLOC 1 : Nous définissons nos options de tri des résultats. Si l’autocomplete par défaut renvoie les résultats par ID croissant, nous préférons ici avoir un affichage trié par ordre alphabétique.
  • BLOC 2 : Si la variable $string (qui correspond à ce que l’utilisateur a renseigné dans son champ de recherche) existe, les critères du nombre de résultats à renvoyer et le type de recherche textuelle à effectuer sont ajoutés et la recherche est lancée.
  • BLOC 3 : Le code balaye chaque résultat de recherche pour en charger l’entité correspondante si elle existe. Si non, le résultat est ignoré, ne sera pas affiché à l’utilisateur et l’on passe au résultat suivant.
  • BLOC 4 : Ce code est similaire à celui de la classe parent. Il définit la valeur qui sera réellement stockée dans le champ une fois que l’utilisateur aura choisi le résultat qu’il souhaite. Il est indispensable que l’identifiant du contenu soit mis entre parenthèses car le champ autocomplete de Drupal se base sur ce critère pour ensuite associer cet identifiant au champ (nous l’avons vu en introduction, la valeur attendue est « 123 » et non pas « article A »).
  • BLOC 5 : Il s’agit de la partie réellement intéressante pour notre besoin. C’est dans ce bloc que nous venons construire le rendu pour l’affichage dans la liste de suggestions. Après avoir récupéré le nom du type de contenu auquel le résultat appartient, les données sont passées à notre méthode custom getFormattedOutput() qui va se charger de construire le rendu avec les données de l’entité souhaitées.
  • BLOC 6 : Chaque ligne de suggestion est insérée dans le tableau $matches qui est retourné pour affichage à l’utilisateur.
>> Mise en forme des données : la méthode getFormattedOutput()

Ajoutons la méthode getFormattedOutput() à notre classe.

/**
 * Returns formatted HTML output for autocomplete suggestion.
 *
 * @param \Drupal\Core\Entity\EntityInterface $entity
 * The entity to format.
 * @param string $bundle_label
 * The human-readable bundle label.
 *
 * @return string
 * Formatted HTML string.
 */
 protected function getFormattedOutput(EntityInterface $entity, string $bundle_label): string {

  $date = match ($entity->getEntityTypeId()) {
   'taxonomy_term' => $entity->getChangedTime(),
   'node' => $entity->getCreatedTime(),
   default => NULL,
  };

  // Affichage du titre, du type de contenu et de l'ID.
  $output = [
  'html' => '<span class="custom-autocomplete-title">$0</span>
           <span class="custom-autocomplete-info">
            <span class="custom-autocomplete-type">$1</span>
            <span class="custom-autocomplete-id">#$2</span>'
,
  'args' => [
    $entity->label(),
    $bundle_label,
    $entity->id(),
  ],
  'replacements' => [
    '$0',
    '$1',
    '$2',
  ],
];

// Si disponible, on ajoute la date de création/modification.
 if ($date) {
  $output['html'] .= ' | <span class="custom-autocomplete-date">$3</span>';
  $output['args'][] = $this->dateFormatter->format($date, 'medium');
  $output['replacements'][] = '$3';
}
// Si l'entité a une méthode pour récupérer l'auteur, on l'affiche aussi.
if (method_exists($entity, 'getOwner')) {
  $output['html'] .= ' | <span class="custom-autocomplete-author">$4</span>';
  $output['args'][] = $entity->getOwner()->label();
  $output['replacements'][] = '$4';
}

$output['html'] .= '</span>';

return '<div class="custom-autocomplete-item">' .
  str_replace($output['replacements'], $output['args'], $output['html']) .
  '</div>';
}

Cette méthode récupère les données que l’on souhaite ajouter à chaque suggestion :

  • le titre du contenu : $entity→label()
  • le nom du type de contenu : $bundle_label
  • l’identifiant unique du contenu : $entity→id()
  • la date de création ou modification du contenu : $entity→getCreatedTime() ou $entity→getChangedTime()
  • l’auteur du contenu : $entity→getOwner()→label()

Toutes ces données sont ensuite insérées dans du code HTML qui est renvoyé à la méthode getMatches().

Si nous reprenons notre exemple de contenus ayant le même titre, voici maintenant le rendu dans notre champ d’autocomplete.

Capture d'écran d'un résultat d'auto-complétion

Nous sommes maintenant en mesure de différencier facilement les suggestions retournées par l’autocomplete !

Amélioration de l’affichage de notre autocomplete

Nous disposons maintenant d’une fonctionnalité avec toutes les données nécessaires pour différencier nos contenus. Il est temps d’appliquer la touche finale en améliorant l’affichage des suggestions via un peu de CSS.

Pour ce faire, nous allons :

  • Déclarer une librairie CSS dans un nouveau fichier custom_autocomplete.libraries.yml
enhanced_link.autocomplete:
 css:
  theme:
   css/enhanced_link.autocomplete.css: {}
  • Attacher cette librairie à nos pages d’administration via le hook hook_page_attachments() que nous utilisons dans le fichier custom_autocomplete.module.
/**
* Implements hook_page_attachments().
*/
function custom_autocomplete_page_attachments(array &$attachments): void {
 if (Drupal::service('router.admin_context')->isAdminRoute()) {
  $attachments['#attached']['library'][] = 'custom_autocomplete/enhanced_link.autocomplete';
 }
}
  • Et enfin, définir un fichier CSS enhanced_link.autocomplete.css dans lequel nous définissons un minimum de style pour améliorer l’affichage.
/* Styles for the enhanced autocomplete link widget */
.ui-autocomplete.ui-widget-content {
 max-height: 500px;
 overflow-y: auto;
}

.ui-autocomplete .ui-menu-item {
 display: block;
 background: white;
 padding-block: 0.5rem;
}

/* Title of the entity */
.custom-autocomplete-item .custom-autocomplete-title {
 display: block;
 font-weight: bold;
}

/* ID of the entity */
.custom-autocomplete-item .custom-autocomplete-id {
 font-weight: bold;
}

/* Author that created the entity */
.custom-autocomplete-item .custom-autocomplete-author {
 font-weight: bold;
}

Et voici le rendu final de notre outil d’autocomplétion ! L’amélioration est flagrante par rapport à notre situation de départ. D’un coup d’oeil, nous savons qui a créé le contenu suggéré et quand, nous savons à quel type de contenu il appartient et enfin l’identifiant unique apparaît.

Résultat après la mise en forme

La structure finale de notre module, quant à elle, est la suivante :

Capture d'écran du résultat de la structure du module

Cet article vous montre qu’avec un minimum de fichiers et de code, il est possible d’améliorer notablement des fonctionnalités proposées par Drupal.

Via une logique simple qui consiste à surcharger un service déjà existant pour l’enrichir avec vos propres critères, vous disposez désormais d’un outil d’autocomplétion qui permettra à vos contributeurs d’utiliser plus facilement le back-office de Drupal.

Co-rédacteur : Vivien Barbeau - Développeur Drupal

Crédit photo : NanoStockk

Image mise en avant pour l'article
Pierre Waldura
Développeur Web Drupal
WEBINAR
IA et Drupal : les innovations clés pour révolutionner 80 % de vos processus Web
Voir le webinar !
Vous avez un projet à nous confier ?
Tout au long de votre projet, nos experts vous conseillent et vous accompagnent
Contactez-nous !