Drupal, créer un champ en auto-complétion
Le 14 novembre 2007, à 18:44 par Ulhume...

Depuis Drupal 5, il est possible très simplement d'ajouter l'auto-complétion à un champ texte en utilisant une méthode dit d'appel asynchrone (aka Ajax). Traduit en français, cette fonctionnalité permet lorsque vos utilisateurs saisissent les premières lettre, de lui fournir automatique une liste de suggestion commençant par ces lettres, lui évitant de taper la suite.

Auto-Complétion

L'auto-complétion (ou suggestion) est donc un appel asynchrone. C'est à dire que lorsque l'utilisateur saisi une lettre dans le champ texte, au bout d'un temps court, une procédure JavaScript va lancer une requête prenant en paramètre le texte qui a été tapé. Le serveur (Drupal) va recevoir cette URL, et exécuter la procédure associée qui a pour tache de construire une liste de suggestion. Ceci fait le serveur renvoi à la procédure javascript le résultat qui va l'utiliser pour fabriquer, à la volée, une petite liste déroulant pour l'utilisateur.

Dans l'exemple qui suit, le champ que nous allons modifier, permet d'obtenir une liste de termes (taxonomie), suggérés à partir de ce que l'utilisateur a saisir, et ceci pour un vocabulaire donné. Il peut être adapté à d'autre exemples facilement.

Implémenter le hook _menu

La première chose que nous avons à faire est donc de déclarer dans Drupal un chemin (path) qui va rediriger cette URL vers une procédure capable de lister les bonnes réponses. Et qui dit path, dit dit un hook _menu :

/**
 * Implementation of hook_menu().
 */

function mon_module_menu($may_cache)
{
  $items= array ();
  if ($may_cache)
  {
      $items[] = array(
      'path' => 'mon_module/categories/autocomplete',
      'title' => t('Auto completion pour mon module/categories'),
      'callback' => 'mon_module_categories_auto_complete',
      'access' => TRUE,
      'type' => MENU_CALLBACK);
  }
  return $items;
}

Pour des raisons de performances, Drupal stocke en base de données (table cache_menu) tous ces chemins (c'est la raison d'être du $may_cache). Du coup, il est préférable après avoir écrit le hook _menu, de donner dans votre console de base de données un petit coup de :
DELETE FROM cache_menu;

Implémenter le callback

Etape suivante, écrire la procédure déclarée dans le hook _menu par le paramètre callback et qui va générer notre liste de suggestions.

function mon_module_categories_autocomplete($string = '')
{
  $matches = array();
  $vid=
  if ($string)
  {
    $cursor = db_query_range("
      SELECT name
      FROM {term_data}
      WHERE vid=10 AND LOWER(name) LIKE LOWER('%s%%')"
, $string, 0, 10);
    while ($term = db_fetch_object($cursor))
      $matches[$term->name] = $term->name;
  }
  print drupal_to_js($matches);
  exit();
}

Quelques explications s'imposent. Tout d'abord le paramètre $string. Ce dernier contient, vous l'aurez deviné, la chaîne à rechercher. Cela correspond à tout ce qui se trouve après le mon_module/categories/autocomplete dans l'url. Cela marche très bien sauf dans un cas, si vous voulez chercher des choses qui contiennent des /. Là ça marche beaucoup moins bien car $string ne va contenir que ce qui se trouve avant le premier / (logique). Du coup, il faut tricher un peu et rajouter une horreur de ce genre au début de la procédure :

$string=substr($_GET['q'], str_len('mon_module/categories/autocomplete/'));

Cela va prendre à la source ($_GET['q']) la totalité de l'URL drupal et enlever la partie qui ne sert à rien (path).

Une fois la variable $string en main, il suffit de faire une requête SQL pour récupérer une liste limitée à 10 items de réponse qui concordent avec notre chaîne. Notez que dans la requête d'exemple, la recherche se fait sur le vocabulaire ayant comme vid la valeur 10. Pensez à changer cela pour utiliser votre propre vid.

Une fois la liste constituée ($matches), on utilise la fonction magique de drupal drupal_to_js qui va créer une petite procédure javascript contenant nos données, utilisable par l'auto-complétion de Drupal.

Avant d'aller plus loin, nous pouvons déjà tester si tout les suggestions fonctionnent, et dans FireFox, rentrer une URL artificiel du style http://localhost/mon_drupal?q=mon_module/categories/autocomplete/a. Une fois exécutée, la page affichée doit contenir tout les termes qui commencent par la lettre a.

Ajouter le champ

Maintenant que l'on sait que notre procédure fonctionne, il ne nous reste plus qu'à ajouter dans un formulaire (soit un mon_module_form_alter, soit un mon_module_form), un champ texte tout con :

$form['categorie']= array (
'#type' => 'textfield', //
'#title' => t('Tapez les premières lettres de la catégorie et laissez-vous guider...'), //
'#required' => TRUE, //
'#autocomplete_path' => 'mon_module/categories/autocomplete');

Rien de bien extraordinaire mis à part le champ '#autocomplete_path' qui est seul responsable de la magie. Il suffit d'afficher votre formulaire pour voir apparaître votre champ texte doté d'un étrange petit cercle gros sur la droite. Ce symbole indique que l'auto-complétion est disponible sur ce champ. Tapez une lettre (a) et attendez une seconde, une liste devrait alors apparaître avec vos termes de vocabulaire.

Conclusion

Voilà comment en peu de ligne vous pouvez changer la vie de vos utilisateurs, leur évitant les anti-ergonomiques liste déroulantes contenant des centaines d'item.

Commentaires

tenshu , le 15 November, 2007 - 12:47

y'a t'il une façon plus simple d'utiliser ajax via jquery et drupal_add_js() ?

Ulhume, le 15 November, 2007 - 13:04

Quel veux-tu dire par "plus simple" ? Pour faire quoi exactement ?

tenshu , le 17 November, 2007 - 19:50

bien si je me goure pas drupal 5 intègre jquery dans son core?
Jquery propose l'auto complétion
Jquery s'utilise avec drupal_add_js()

Donc n'y a t'il pas moyen de faire plus simple?

Je m'explique mieux?

Ulhume, le 17 November, 2007 - 20:07

Ben non, pas à ma connaissance du moins. Le code qui est ici sert surtout à mettre en place le champ, puis la source de données correspondant à l'auto-complétion de ce champ (interrogation de base + drupal_add_js()). Donc si tu pars d'un besoin nouveau (comme ici), il ne peut y avoir moins de code.

En revanche, si tu veux faire l'auto-complétion sur un champ utilisateur par exemple, il y a juste la bonne call-back à indiquer dans la définition du champ et rien d'autre. C'est ce que tu veux dire par plus simple ? Car sinon, je vois mal comment faire plus simple que ce qui est exposé ici, en tout cas avec autant de souplesse.

tenshu , le 17 November, 2007 - 20:35

ok je débute avec drupal Wink

Ulhume, le 18 November, 2007 - 00:07

Pas de soucis Smiling n'hésites pas en tout cas !

gagarine , le 23 November, 2007 - 11:32

l'auto-completion c'est cool mais je trouve qu'il faudrait pouvoir afficher plus d'info.
Par exemple si je des node de ville, Fribourg par exemple Wink.

Je tape Freibourg et drupal vas me présenter deux résultats:

-Freibourg
-Freibourg

Un est Freibourg CH et l'autre Freibourg DE Il faudrait donc pouvoir aller rechercher plus d'info sur ma ville pour avoir quelque chose comme ça:

-Freibourg CH
-Freibourg DE

et une question en plus... Smiling si on a énormément de node comme faire pour placer une sorte de recherche qui vas pres séléctionner les nodes?

Ulhume, le 23 November, 2007 - 14:30

Dans la requête SQL donnée en exemple, je ne sélectionne que le champ "name", rien ne t'empêches cependant de faire une sélection plus large, remontant plus de champs, ou même de faire une jointure pour faire une recherche sur plusieurs tables. Par exemple avec un node de type "ville" et une table associée "extension" qui contient l'information de pays, cela donnerait :

    $cursor = db_query_range("
      SELECT n.title, e.pays
      FROM node n
                        INNER JOIN e extention ON e.nid=n.nid
      WHERE LOWER(e.title) LIKE LOWER('%s%%')"
, $string, 0, 10);
    while ($term = db_fetch_object($cursor))
      $matches[$term->name] = $term->title.' '.$term->pays;

Et ce type de requête, pourvu que tu ais des indexes, ne pose pas de problème même sur un grand nombre de node, le paramètre (10) fait que tu ne remontes que 10 entrées (en fait Drupal modifie la requête pour ajouter un simple LIMIT 10 à la fin). Pas besoin donc de préselectionner quoi que ce soit, si j'ai bien compris ta question.

Anonymous , le 23 November, 2007 - 18:22

Ok merci très intéressant... tu réponds parfaitement à ma première question Smiling

La deuxième question n'est pas tout a fait en rapport avec le post.

En faite ce que j'aimerai faire c'est par exemple avoir deux champs de recherches liée. Pour continuer dans la même voix:

[payse] [ville]

Le deuxième étant grisé tant que je n'ai pas sélectionné le 1er. Je choisie donc d'abord le pays et dans le deuxième champs je n'ai plus que des propositions de villes du pays en question.

En gros permettre à l'utilisateur d'envoyer des paramètre de recherches avant de choisir le node pour limiter les résultats.

Ca peut être très utile si je sais que je veux sélectionner une ville Suisse me ne me rappelle plus son nom (évidement dans ce cas le deuxième champs doit être une simple liste déroulant).

J'espère avoir été plus clair Smiling

Poster un nouveau commentaire

Le contenu de ce champ est gardé secret et ne sera pas montré publiquement.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • To highlight piece of code, just surround them with <code type="language"> Your code &tl;/code>>. Language can be java,c++,bash,etc... Everything Geshi support.
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • Textual smileys will be replaced with graphical ones.
  • Les adresses de pages web et de messagerie électronique sont transformées en liens automatiquement.

Plus d'informations sur les options de formatage

Connexion utilisateur
Les derniers bavardages...