<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Artisan Numérique</title>
  <link rel="alternate" type="text/html" href="http://artisan.karma-lab.net/node/1259"/>
  <link rel="self" type="application/atom+xml" href="http://artisan.karma-lab.net/node/1259/atom/feed"/>
  <id>http://artisan.karma-lab.net/node/1259/atom/feed</id>
  <updated>2008-11-14T08:30:29+01:00</updated>
  <entry>
    <title>Drupal, créer un champ en auto-complétion</title>
    <link rel="alternate" type="text/html" href="http://artisan.karma-lab.net/node/1259" />
    <id>http://artisan.karma-lab.net/node/1259</id>
    <published>2007-11-14T18:44:16+01:00</published>
    <updated>2008-11-14T08:30:29+01:00</updated>
    <author>
      <name>Ulhume</name>
    </author>
    <category term="Drupal" />
    <category term="drupalfr.org" />
    <category term="OK" />
    <category term="Planet Libre" />
    <category term="Tutoriel" />
    <summary type="html"><![CDATA[<p>
  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. 
</p>
    ]]></summary>
    <content type="html"><![CDATA[<p>
  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. 
</p>
<!--break-->

	<a name='chapter_1'></a>
  <h2>Auto-Complétion</h2>
	
<p>
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. 
</p>
<p>
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. 
</p>

	<a name='chapter_2'></a>
  <h2>Implémenter le hook <kbd>_menu</kbd></h2>
	
<p>
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 <kbd>_menu</kbd> :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="coMULTI">/**<br />
&nbsp;* Implementation of hook_menu().<br />
&nbsp;*/</span><br />
<a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> mon_module_menu<span class="br0">&#40;</span><span class="re0">$may_cache</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$items</span><span class="sy0">=</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$may_cache</span><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span class="re0">$items</span><span class="br0">&#91;</span><span class="br0">&#93;</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'path'</span> <span class="sy0">=&gt;</span> <span class="st0">'mon_module/categories/autocomplete'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'title'</span> <span class="sy0">=&gt;</span> t<span class="br0">&#40;</span><span class="st0">'Auto completion pour mon module/categories'</span><span class="br0">&#41;</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'callback'</span> <span class="sy0">=&gt;</span> <span class="st0">'mon_module_categories_auto_complete'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'access'</span> <span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'type'</span> <span class="sy0">=&gt;</span> MENU_CALLBACK<span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <span class="br0">&#125;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a> <span class="re0">$items</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span>
  </div>
  
  </div>
</p>
<div class='inline-box attention'>
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 <kbd>$may_cache</kbd>). 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 :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="kw1">DELETE</span> <span class="kw1">FROM</span> cache_menu;
  </div>
  
  </div>
</div>

	<a name='chapter_3'></a>
  <h2>Implémenter le callback</h2>
	
<p>
  Etape suivante, écrire la procédure déclarée dans le hook _menu par le paramètre <kbD>callback</kbd> et qui va générer notre liste de suggestions. 

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> mon_module_categories_autocomplete<span class="br0">&#40;</span><span class="re0">$string</span> <span class="sy0">=</span> <span class="st0">''</span><span class="br0">&#41;</span><br />
<span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$matches</span> <span class="sy0">=</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <span class="re0">$vid</span><span class="sy0">=</span><br />
&nbsp; <a target="blank" href="http://www.php.net/if"><span class="kw1">if</span></a> <span class="br0">&#40;</span><span class="re0">$string</span><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re0">$cursor</span> <span class="sy0">=</span> db_query_range<span class="br0">&#40;</span><span class="st0">&quot;<br />
&nbsp; &nbsp; &nbsp; SELECT name<br />
&nbsp; &nbsp; &nbsp; FROM {term_data}<br />
&nbsp; &nbsp; &nbsp; WHERE vid=10 AND LOWER(name) LIKE LOWER('%s%%')&quot;</span><span class="sy0">,</span> <span class="re0">$string</span><span class="sy0">,</span> <span class="nu0">0</span><span class="sy0">,</span> <span class="nu0">10</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; &nbsp; <a target="blank" href="http://www.php.net/while"><span class="kw1">while</span></a> <span class="br0">&#40;</span><span class="re0">$term</span> <span class="sy0">=</span> db_fetch_object<span class="br0">&#40;</span><span class="re0">$cursor</span><span class="br0">&#41;</span><span class="br0">&#41;</span><br />
&nbsp; &nbsp; &nbsp; <span class="re0">$matches</span><span class="br0">&#91;</span><span class="re0">$term</span><span class="sy0">-&gt;</span><span class="me1">name</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="re0">$term</span><span class="sy0">-&gt;</span><span class="me1">name</span><span class="sy0">;</span><br />
&nbsp; <span class="br0">&#125;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/print"><span class="kw3">print</span></a> drupal_to_js<span class="br0">&#40;</span><span class="re0">$matches</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/exit"><span class="kw3">exit</span></a><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span>
  </div>
  
  </div>
</p>
<p>
Quelques explications s'imposent. Tout d'abord le paramètre <kbd>$string</kbd>. Ce dernier contient, vous l'aurez deviné, la chaîne à rechercher. Cela correspond à tout ce qui se trouve après le <kbd>mon_module/categories/autocomplete</kbd> dans l'url. Cela marche très bien sauf dans un cas, si vous voulez chercher des choses qui contiennent des <kbd>/</kbd>. Là ça marche beaucoup moins bien car <kbd>$string</kbd> ne va contenir <i>que</i> ce qui se trouve avant le premier <kbd>/</kbd> (logique). Du coup, il faut tricher un peu et rajouter une horreur de ce genre au début de la procédure :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="re0">$string</span><span class="sy0">=</span><a target="blank" href="http://www.php.net/substr"><span class="kw3">substr</span></a><span class="br0">&#40;</span><span class="re0">$_GET</span><span class="br0">&#91;</span><span class="st0">'q'</span><span class="br0">&#93;</span><span class="sy0">,</span> str_len<span class="br0">&#40;</span><span class="st0">'mon_module/categories/autocomplete/'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
  </div>
  
  </div>
</p>
<p>
  Cela va prendre à la source ($_GET['q']) la totalité de l'URL drupal et enlever la partie qui ne sert à rien (path). 
</p>
<p>
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. 
</p>
<p>
Une fois la liste constituée ($matches), on utilise la fonction magique de drupal <kbd>drupal_to_js</kbd> qui va créer une petite procédure javascript contenant nos données, utilisable par l'auto-complétion de Drupal. 
</p>
<p>
Avant d'aller plus loin, nous pouvons déjà tester si tout les suggestions fonctionnent, et dans FireFox, rentrer une URL artificiel du style <kbd>http://localhost/mon_drupal?q=mon_module/categories/autocomplete/a</kbd>. Une fois exécutée, la page affichée doit contenir tout les termes qui commencent par la lettre <kbd>a</kbd>. 
</p>

	<a name='chapter_4'></a>
  <h2>Ajouter le champ</h2>
	
<p>
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 :

  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="re0">$form</span><span class="br0">&#91;</span><span class="st0">'categorie'</span><span class="br0">&#93;</span><span class="sy0">=</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><br />
<span class="st0">'#type'</span> <span class="sy0">=&gt;</span> <span class="st0">'textfield'</span><span class="sy0">,</span> <span class="co1">//</span><br />
<span class="st0">'#title'</span> <span class="sy0">=&gt;</span> t<span class="br0">&#40;</span><span class="st0">'Tapez les premières lettres de la catégorie et laissez-vous guider...'</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="co1">//</span><br />
<span class="st0">'#required'</span> <span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="sy0">,</span> <span class="co1">//</span><br />
<span class="st0">'#autocomplete_path'</span> <span class="sy0">=&gt;</span> <span class="st0">'mon_module/categories/autocomplete'</span><span class="br0">&#41;</span><span class="sy0">;</span>
  </div>
  
  </div>
</p>
<p>
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. 
</p>

	<a name='chapter_5'></a>
  <h2>Conclusion</h2>
	
<p>
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. 
</p>    ]]></content>
  </entry>
</feed>
