<?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/1653"/>
  <link rel="self" type="application/atom+xml" href="http://artisan.karma-lab.net/node/1653/atom/feed"/>
  <id>http://artisan.karma-lab.net/node/1653/atom/feed</id>
  <updated>2008-09-29T21:47:38+02:00</updated>
  <entry>
    <title>Créer un module Drupal : AHAH et formulaires dynamiques</title>
    <link rel="alternate" type="text/html" href="http://artisan.karma-lab.net/node/1653" />
    <id>http://artisan.karma-lab.net/node/1653</id>
    <published>2008-09-24T16:37:18+02:00</published>
    <updated>2008-09-29T21:47:38+02: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>
   Les formulaires dynamiques sont vieux comme le client-serveur. Cela peut correspondre par exemple à une liste principale dont le choix d'un élément déclenche la population d'une liste secondaire. Rien de bien sorcier donc, mais comme pour pas mal d'autres de choses, ce qui était relativement simple à coder avec un 
  <a target='_blank' href='http://fr.wikipedia.org/wiki/RAD'>
  RAD
  </a> comme Delphi ou même Visual Basic, est devenu un véritable enfer avec la mode des applications WEB. Voyons donc comment faire ce type de chose avec la dernière <a class='external' target='_blank' href='http://api.drupal.org/api/file/developer/topics/forms_api_reference.html/6' >Form API de Drupal 6</a>.
</p>
    ]]></summary>
    <content type="html"><![CDATA[<p>
   Les formulaires dynamiques sont vieux comme le client-serveur. Cela peut correspondre par exemple à une liste principale dont le choix d'un élément déclenche la population d'une liste secondaire. Rien de bien sorcier donc, mais comme pour pas mal d'autres choses, ce qui était relativement simple à coder avec un 
  <a target='_blank' href='http://fr.wikipedia.org/wiki/RAD'>
  RAD
  </a> comme Delphi, ou même Visual Basic, est devenu un véritable enfer avec la mode des applications WEB. Voyons donc comment faire ce type de chose avec la dernière <a class='external' target='_blank' href='http://api.drupal.org/api/file/developer/topics/forms_api_reference.html/6' >Form API de Drupal 6</a>.
</p>
<!--break-->

	<a name='chapter_1'></a>
  <h2>Sources</h2>
	
<p>
   Les sources du module d'exemple courses sont disponibles <a class='external' target='_blank' href='/node/1654' >ici</a>.
</p>

	<a name='chapter_2'></a>
  <h2>Mise à jour du schéma</h2>
	
<p>
  Comme depuis un moment déjà nous allons continuer à torpiller notre <a class='external' target='_blank' href='/node/1561' >liste de courses</a> à qui nous avions récemment ajouté <a class='external' target='_blank' href='/node/1618' >les nouveaux schémas</a> de Drupal 6. 
</p>
<p>
  Pour les besoins de l'expérience, nous allons ajouter deux nouveaux champs à notre table <kbd>node_produit</kbd> : <kbd>categorie_produit</kbd> et <kbd>type_produit</kbd>. Le but est simple, lors de l'ajout ou de l'édition d'un produit, nous aurons une liste permettant de choisir la catégorie (féculents, jus de fruits, légumes, etc.). Et lorsque l'utilisateur sélectionnera un élément de cette liste, cela déclenchera la mise à jour d'une seconde liste de types de produit (pâtes, choux, jus d'orange, etc.). Un peu neu-neu, je sais, mais au moins on peut se concentrer sur la technique. 
</p>
<p>
   Pour commencer nous allons rapidement modifier le schéma de notre module (courses.install) en ajoutant nos deux champs :
   
  <div class='code-block code-block-fragment'>
  <div class='container'>
  &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'type_produit'</span> <span class="sy0">=&gt;</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; &nbsp; &nbsp; &nbsp; <span class="st0">'description'</span> &nbsp; <span class="sy0">=&gt;</span> <span class="st0">'Type du produit (pâtes, courgettes, etc...)'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'type'</span> &nbsp; &nbsp; &nbsp;<span class="sy0">=&gt;</span> <span class="st0">'int'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'unsigned'</span> &nbsp; &nbsp;<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; &nbsp; &nbsp; &nbsp; <span class="st0">'not null'</span> &nbsp; &nbsp;<span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="br0">&#41;</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'categorie_produit'</span> <span class="sy0">=&gt;</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; &nbsp; &nbsp; &nbsp; <span class="st0">'description'</span> &nbsp; <span class="sy0">=&gt;</span> <span class="st0">'Catégorie de produit'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'type'</span> &nbsp; &nbsp; &nbsp;<span class="sy0">=&gt;</span> <span class="st0">'int'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span class="st0">'unsigned'</span> &nbsp; &nbsp;<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; &nbsp; &nbsp; &nbsp; <span class="st0">'not null'</span> &nbsp; &nbsp;<span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="br0">&#41;</span><span class="sy0">,</span> &nbsp;
  </div>
  <div class='caption'>courses.install - courses_schema()</div>
  </div>
</p>

<p>
  Ensuite, il nous faut implémenter un nouveau <kbd>hook_update</kbd> pour permettre la mise à jour des anciens schémas :
   
  <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> courses_update_2<span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; <span class="re0">$ret</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; db_add_field<span class="br0">&#40;</span><span class="re0">$ret</span><span class="sy0">,</span> <span class="st0">'node_produit'</span><span class="sy0">,</span> <span class="st0">'categorie_produit'</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; <span class="st0">'description'</span> &nbsp; <span class="sy0">=&gt;</span> <span class="st0">'Catégorie de produit'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; <span class="st0">'type'</span> &nbsp; &nbsp; &nbsp;<span class="sy0">=&gt;</span> <span class="st0">'int'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; <span class="st0">'unsigned'</span> &nbsp; &nbsp;<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; <span class="st0">'not null'</span> &nbsp; &nbsp;<span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; db_add_field<span class="br0">&#40;</span><span class="re0">$ret</span><span class="sy0">,</span> <span class="st0">'node_produit'</span><span class="sy0">,</span> <span class="st0">'type_produit'</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; <span class="st0">'description'</span> &nbsp; <span class="sy0">=&gt;</span> <span class="st0">'Type du produit (pâtes, courgettes, etc...)'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; <span class="st0">'type'</span> &nbsp; &nbsp; &nbsp;<span class="sy0">=&gt;</span> <span class="st0">'int'</span><span class="sy0">,</span><br />
&nbsp; &nbsp; <span class="st0">'unsigned'</span> &nbsp; &nbsp;<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; <span class="st0">'not null'</span> &nbsp; &nbsp;<span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/true"><span class="kw2">TRUE</span></a><span class="br0">&#41;</span><br />
&nbsp; <span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a> <span class="re0">$ret</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span> &nbsp;
  </div>
  <div class='caption'>courses.install - courses_update_2()</div>
  </div>
</p>
<p>
  Ceci fait, lancez la procédure de mise à jour de Drupal (update.php) au terme de laquelle, note table devrait être modifiée. 
</p>

	<a name='chapter_3'></a>
  <h2>Source de données</h2>
	
<p>
  Pour une véritable application, nos données <kbd>catégories</kbd> et <kbd>types</kbd> seraient proprement stockées en base de donnée. Ici, nous allons faire simple avec deux fonctions en dur :
  
  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="kw1">function</span> courses_categories<span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; <span class="kw3">return</span> array<span class="br0">&#40;</span><br />
&nbsp; &nbsp; <span class="nu0">0</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">&quot;Catégorie du produit&quot;</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; <span class="nu0">1</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">&quot;Légumes&quot;</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; <span class="nu0">2</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">&quot;Féculents&quot;</span><span class="br0">&#41;</span>, <br />
&nbsp; &nbsp; <span class="nu0">3</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">&quot;Jus de fruit&quot;</span><span class="br0">&#41;</span>, <br />
&nbsp; <span class="br0">&#41;</span>;<br />
<span class="br0">&#125;</span><br />
<br />
<span class="kw1">function</span> courses_types<span class="br0">&#40;</span><span class="re1">$category</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; switch <span class="br0">&#40;</span><span class="re1">$category</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="kw1">case</span> <span class="nu0">1</span>: <span class="br0">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span class="kw3">return</span> array <span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="nu0">1</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Choux'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="nu0">2</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Courgettes'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="nu0">3</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Carottes'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="br0">&#41;</span>;<br />
&nbsp; &nbsp; <span class="br0">&#125;</span><br />
&nbsp; &nbsp; <span class="kw1">case</span> <span class="nu0">2</span>: <span class="br0">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span class="kw3">return</span> array <span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="nu0">1</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Spaghetties'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="nu0">2</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Penne Rigate'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="nu0">3</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">'Vermicelles'</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="br0">&#41;</span>;<br />
&nbsp; &nbsp; <span class="br0">&#125;</span><br />
&nbsp; &nbsp; <span class="kw1">case</span> <span class="nu0">3</span>: <span class="br0">&#123;</span><br />
&nbsp; &nbsp; &nbsp; <span class="kw3">return</span> array <span class="br0">&#40;</span><br />
&nbsp; &nbsp; &nbsp; <span class="nu0">1</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">&quot;Jus d'orange&quot;</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="nu0">2</span>=<span class="sy0">&gt;</span>t<span class="br0">&#40;</span><span class="st0">&quot;Jus de pamplemousse&quot;</span><span class="br0">&#41;</span>,<br />
&nbsp; &nbsp; &nbsp; <span class="br0">&#41;</span>;<br />
&nbsp; &nbsp; <span class="br0">&#125;</span><br />
&nbsp; <span class="br0">&#125;</span><br />
<span class="br0">&#125;</span> &nbsp; &nbsp;
  </div>
  <div class='caption'>courses.module</div>
  </div>
</p>


	<a name='chapter_4'></a>
  <h2>Liste maître-esclave</h2>
	
<p>
  A l'ancienne mode, cela aurait consisté à utiliser le très vilain attribut de formulaire <kbd>DANGEROUS_SKIP_CHECK</kbd> et ajouter un bouton qui provoque une validation intermédiaire. Aujourd'hui trois arguments s'y opposent. Tout d'abord <kbd>DANGEROUS_SKIP_CHECK</kbd> a été supprimé. Ensuite les formulaires sont tous mis en cache et donc difficile à modifier dynamiquement. C'est ceci dit faisable en utilisant l'attribut de champ <kbd>#process</kbd> et de formulaire <kbd>#REBUILD</kbd> mais le fait de ne pas pouvoir by-passer les contrôles implique qu'à chaque mise à jour de la liste s'affiche des erreurs de validation, c'est moche. Enfin dernier argument, c'est pas AJAX donc c'est pas bien, on m'a dit...
</p>
<p>
  Le framework AHAH qui était un module pour Drupal 5, fait aujourd'hui parti du coeur de Drupal 6. Cette librairie utilise jQuery pour ajouter à Drupal cette giclée d'AJAX qui lui manquait temps.  La différence entre AJAX et AHAH (Asynchronous HTML over HTTP, me demandez pas pourquoi) est que le résultat de la réponse est du XHTML qui est directement collée dans le document en cours avec de petits effets genre glissement, fondus, etc...
</p>
<p>
 Il faut donc voir AHAH comme un sous-ensemble fonctionnel d'AJAX et cela va nous suffire car c'est exactement ce dont nous avons besoin. 
</p>
<p>
  L'intégration dans un formulaire d'AHAH est relativement directe. Pour nous deux listes, cela donne ceci (à placer à la tête de la fonction <kbd>courses_form</kbd> :
  
    
  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="re1">$form</span><span class="br0">&#91;</span><span class="st0">'categorie_produit'</span><span class="br0">&#93;</span> = array<span class="br0">&#40;</span><br />
&nbsp; <span class="st0">'#type'</span> =<span class="sy0">&gt;</span> <span class="st0">'select'</span>,<br />
&nbsp; <span class="st0">'#title'</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">'Catégorie'</span><span class="br0">&#41;</span>,<br />
&nbsp; <span class="st0">'#options'</span> =<span class="sy0">&gt;</span> courses_categories<span class="br0">&#40;</span><span class="br0">&#41;</span>,<br />
&nbsp; <span class="st0">'#default_value'</span>=<span class="sy0">&gt;</span><span class="re1">$node</span>-<span class="sy0">&gt;</span>categorie_produit,<br />
&nbsp; <span class="st0">'#description'</span> =<span class="sy0">&gt;</span> t<span class="br0">&#40;</span><span class="st0">'Sélectionnez une catégorie'</span><span class="br0">&#41;</span>,<br />
&nbsp; <span class="st0">'#required'</span> =<span class="sy0">&gt;</span> TRUE,<br />
&nbsp; <span class="st0">'#ahah'</span> =<span class="sy0">&gt;</span> array<span class="br0">&#40;</span><br />
&nbsp; &nbsp; <span class="st0">'path'</span> =<span class="sy0">&gt;</span> <span class="st0">'courses/js/types'</span>,<br />
&nbsp; &nbsp; <span class="st0">'wrapper'</span> =<span class="sy0">&gt;</span> <span class="st0">'wrapper-types'</span>,<br />
&nbsp; &nbsp; <span class="st0">'method'</span> =<span class="sy0">&gt;</span> <span class="st0">'replace'</span>,<br />
&nbsp; &nbsp; <span class="st0">'effect'</span> =<span class="sy0">&gt;</span> <span class="st0">'fade'</span><span class="br0">&#41;</span>, &nbsp;<br />
<span class="br0">&#41;</span>;<br />
<br />
<span class="re1">$form</span><span class="br0">&#91;</span><span class="st0">'wrapper-types'</span><span class="br0">&#93;</span> = array<span class="br0">&#40;</span><br />
&nbsp; <span class="st0">'#prefix'</span> =<span class="sy0">&gt;</span> <span class="st0">'&lt;div id=&quot;wrapper-types&quot;&gt;'</span>,<br />
&nbsp; <span class="st0">'#suffix'</span> =<span class="sy0">&gt;</span> <span class="st0">'&lt;/div&gt;'</span>,<br />
&nbsp; <span class="st0">'type_produit'</span> =<span class="sy0">&gt;</span> courses_types_field<span class="br0">&#40;</span><span class="re1">$node</span>-<span class="sy0">&gt;</span>categorie_produit, <span class="re1">$node</span>-<span class="sy0">&gt;</span>type_produit<span class="br0">&#41;</span>,<br />
<span class="br0">&#41;</span>;
  </div>
  <div class='caption'>courses.module - courses_form()</div>
  </div>
</p>
<p>
  Simple mais nécessitant un peu d'explication. Le début de l'élément de formulaire <kbd>categorie_produit</kbd> ne change pas par rapport à ce que nous connaissions. Elle est alimentée par la fonction <kbd>courses_categories()</kbd> que nous avons défini plus haut et utilise <kbd>$node->categorie_produit</kbd> comme valeur par défaut. 
</p>
<p>
  Là où cela change, c'est justement avec l'attribut <kbd>#ahah</kbd> qui définit un comportement AJAX, pardon AHAH,  que la liste doit adopter. Le bloc AHAH n'ayant pas d'attribut <kbd>event</kbd>, va aller se connecter à l'événement par défaut, à savoir la sélection d'un élément de la liste. Nous aurions pu rajouter un <kbd>'event'=>'mousedown'</kbd> mais cela n'aurait pas grand intérêt. <kbd>wrapper</kbd> indique l'ID d'un DIV qui va recevoir les données, <kbd>method</kbd> dit que cette réception doit donner lieu à un remplacement de ce que contenait le DIV (cela pourrait être <kbd>before</kbd> ou <kbd>after</kbd>), <kbd>effect</kbd>, c'est pour faire joli, mettez <kbd>none</kbd> si vous n'aimez pas, et enfin <kbd>path</kbd> est l'URL vers laquelle le module AHAH doit émettre une requête pour recevoir ce fameux contenu à coller dans le DIV. 
</p>
<p>
  Ce fameux DIV est défini par l'élément de formulaire suivant avec comme ID, celui qui a été donné plus haut, et comme contenu notre fameux champ dynamique. Et comme il est dynamique, sa construction est placée dans une fonction que nous allons maintenant définir :
  
  <div class='code-block code-block-fragment'>
  <div class='container'>
  &nbsp; <a target="blank" href="http://www.php.net/function"><span class="kw2">function</span></a> courses_types_field<span class="br0">&#40;</span><span class="re0">$categorie</span><span class="sy0">=</span><a target="blank" href="http://www.php.net/null"><span class="kw2">null</span></a><span class="sy0">,</span><span class="re0">$type</span><span class="sy0">=</span><a target="blank" href="http://www.php.net/null"><span class="kw2">null</span></a><span class="br0">&#41;</span> <span class="br0">&#123;</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="sy0">!</span><a target="blank" href="http://www.php.net/empty"><span class="kw3">empty</span></a><span class="br0">&#40;</span><span class="re0">$categorie</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re0">$types</span><span class="sy0">=</span>courses_types<span class="br0">&#40;</span><span class="re0">$categorie</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <span class="br0">&#125;</span> <a target="blank" href="http://www.php.net/else"><span class="kw1">else</span></a> <span class="br0">&#123;</span><br />
&nbsp; &nbsp; <span class="re0">$types</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="br0">&#125;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/array_unshift"><span class="kw3">array_unshift</span></a><span class="br0">&#40;</span><span class="re0">$types</span><span class="sy0">,</span> t<span class="br0">&#40;</span><span class="st0">&quot;Type de produit&quot;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span><br />
&nbsp; <a target="blank" href="http://www.php.net/return"><span class="kw1">return</span></a> <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">'#type'</span> <span class="sy0">=&gt;</span> <span class="st0">'select'</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">'Type'</span><span class="br0">&#41;</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#options'</span> <span class="sy0">=&gt;</span> <span class="re0">$types</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#description'</span> <span class="sy0">=&gt;</span> t<span class="br0">&#40;</span><span class="st0">'Sélectionnez un type'</span><span class="br0">&#41;</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#default_value'</span><span class="sy0">=&gt;</span><span class="re0">$type</span><span class="sy0">,</span><br />
&nbsp; &nbsp; &nbsp; <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><br />
&nbsp; &nbsp; &nbsp; <span class="st0">'#disabled'</span><span class="sy0">=&gt;</span>count<span class="br0">&#40;</span><span class="re0">$types</span><span class="br0">&#41;</span><span class="sy0">==</span><span class="nu0">1</span><span class="sy0">,</span><br />
&nbsp; &nbsp; <span class="br0">&#41;</span><span class="sy0">;</span><br />
<span class="br0">&#125;</span>
  </div>
  <div class='caption'>courses.module - courses_types_field()</div>
  </div>
</p>
<p>
  Rien de compliqué là dedans, il s'agit juste de la récupération de la bonne liste de types en fonction de la catégorie et éventuellement de la définition d'une position par défaut si le paramètre <kbd>$type</kbd> est renseigné (cas de l'édition d'un produit). 
</p>
<p>
   Voilà, le décor est en place, passons à la partie rock'n'roll, la réponse à la requête AHAH.
</p>


	<a name='chapter_5'></a>
  <h2>Requête AHAH</h2>
	
<p>
   Comme nous l'avons vu, le module AHAH est censé lorsque l'utilisateur sélectionne un élément de la liste <kbd>categories</kbd>, émettre une requête vers <kbd>courses/js/types</kbd> de sorte à recevoir le nouvel élément de formulaire qui va aller remplacer l'ancien. Pour que Drupal sache répondre à cette requête, il faut donc déjà rajouter un nouveau menu (à placer avant le <kbd>return $items</kbd> :
  
  <div class='code-block code-block-fragment'>
  <div class='container'>
  &nbsp; <span class="re0">$items</span><span class="br0">&#91;</span><span class="st0">'courses/js/types'</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; <span class="st0">'page callback'</span> <span class="sy0">=&gt;</span> <span class="st0">'courses_js_types'</span><span class="sy0">,</span><br />
&nbsp; <span class="st0">'type'</span> <span class="sy0">=&gt;</span> MENU_CALLBACK<span class="sy0">,</span><br />
&nbsp; <span class="st0">'access callback'</span> <span class="sy0">=&gt;</span> <span class="st0">'node_access'</span><span class="sy0">,</span><br />
&nbsp; <span class="st0">'access arguments'</span> <span class="sy0">=&gt;</span> <a target="blank" href="http://www.php.net/array"><span class="kw3">array</span></a> <span class="br0">&#40;</span><span class="st0">'view'</span><span class="sy0">,</span><span class="nu0">1</span><span class="br0">&#41;</span><br />
<span class="br0">&#41;</span><span class="sy0">;</span>
  </div>
  <div class='caption'>courses.module - courses_menu()</div>
  </div>
</p>
<p>
  Rien de nouveau ici, cela reprend la technique plus laborieuse que j'avais utilisée pour faire causer <a class='external' target='_blank' href='/node/1273' >jQuery avec Drupal</a>. Il nous reste donc à ajouter notre <kbd>callback</kbd> :
  
  <div class='code-block code-block-fragment'>
  <div class='container'>
  <span class="kw1">function</span> courses_js_types<span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="br0">&#123;</span><br />
&nbsp; <span class="sy0">//</span> Récupération de la <a target="blank" href="http://pwet.fr/man/linux/commandes/cat"><span class="kw2">cat</span></a>égorie<br />
&nbsp; <span class="re1">$categorie</span>=<span class="re1">$_POST</span><span class="br0">&#91;</span><span class="st0">'categorie_produit'</span><span class="br0">&#93;</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Fabrication de notre élément avec la bonen <a target="blank" href="http://pwet.fr/man/linux/commandes/cat"><span class="kw2">cat</span></a>égorie<br />
&nbsp; <span class="re1">$element</span>=courses_types_field<span class="br0">&#40;</span><span class="re1">$categorie</span><span class="br0">&#41;</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Récupération de l<span class="st0">'ID unique du formulaire<br />
&nbsp; $form_build_id = $_POST['</span>form_build_id<span class="st0">'];<br />
<br />
&nbsp; // On fabrique un faux form_state<br />
&nbsp; $form_state = array('</span>submitted<span class="st0">' =&gt; FALSE);<br />
<br />
&nbsp; // Récupération du formulaire à partir du cache<br />
&nbsp; $form = form_get_cache($form_build_id, $form_state);<br />
<br />
&nbsp; // On ajoute notre élément dynamique dans le formulaire (en fait, on remplace l'</span>ancien...<span class="br0">&#41;</span><br />
&nbsp; <span class="re1">$form</span><span class="br0">&#91;</span><span class="st0">'wrapper-types'</span><span class="br0">&#93;</span><span class="br0">&#91;</span><span class="st0">'type_produit'</span><span class="br0">&#93;</span>=<span class="re1">$element</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Sauvegarde <a target="blank" href="http://pwet.fr/man/linux/commandes/du"><span class="kw2">du</span></a> formulaire dans le cache<br />
&nbsp; form_set_cache<span class="br0">&#40;</span><span class="re1">$form_build_id</span>, <span class="re1">$form</span>, <span class="re1">$form_state</span><span class="br0">&#41;</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Reconstruction <a target="blank" href="http://pwet.fr/man/linux/commandes/du"><span class="kw2">du</span></a> formulaire<br />
&nbsp; <span class="re1">$form</span> = form_builder<span class="br0">&#40;</span><span class="re1">$_POST</span><span class="br0">&#91;</span><span class="st0">'form_id'</span><span class="br0">&#93;</span>, <span class="re1">$form</span>, <span class="re1">$form_state</span><span class="br0">&#41;</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Récupération de notre élément reconstruit<br />
&nbsp; <span class="re1">$element</span> = <span class="re1">$form</span><span class="br0">&#91;</span><span class="st0">'wrapper-types'</span><span class="br0">&#93;</span><span class="br0">&#91;</span><span class="st0">'type_produit'</span><span class="br0">&#93;</span>;<br />
<br />
&nbsp; <span class="sy0">//</span> Transformation de l<span class="st0">'élément en HTML<br />
&nbsp; $output = drupal_render($element)<br />
<br />
&nbsp; // On renvoie au client le formulaire sous sa forme HTML, convertie en JSON<br />
&nbsp; print drupal_to_js(array('</span>data<span class="st0">' =&gt; $output, '</span>status<span class="st0">' =&gt; true));<br />
&nbsp; exit();<br />
} &nbsp;<br />
</span
  </div>
  
  </div>
</p>
<p>
  Alors oui, j'en conviens, c'est un peu "sportif". L'idée est que AHAH ne fait pas un GET mais un POST du formulaire dans son état courant. Du coup, nous avons toutes les valeurs que l'utilisateur a déjà saisies, dont la catégorie, dans la variable <kbd>$_POST</kbd>. Cela nous permet déjà de construire notre élément dynamique. 
</p>
<p>
  Une valeur un peu étonnante envoyée par POST est <kbd>form_build_id</kbd>. Il s'agit de l'ID unique de l'instance du formulaire pour cet utilisateur. Et nous allons utiliser cet ID pour aller faucher dans le cache le formulaire complet tel que Drupal l'a sauvegardé avant de l'envoyer. Ensuite nous allons remplacer dans ce formulaire l'ancien élément <kbd>type_produit</kbd> par le nouveau et sauver le tout dans le cache. Alors pourquoi se compliquer la vie ainsi ? Simplement pour tromper Drupal et lui faire croire que le formulaire que nous sommes en train de modifier dans son dos est le même que celui qu'il a originellement envoyé à l'utilisateur. 
</p>
<p>
  Pour terminer, nous allons utiliser la fonction <kbd>form_builder</kbd> qui va régénérer le formulaire dans le même état que si Drupal était sur le point de l'envoyer. La seule différence est que nous allons extraire notre élément 'type_produit' de ce formulaire regénéré pour le passer à la fonction <kbd>drupal_render</kbd> qui va le transformer en code XHTML. 
</p>
<p>
  Dernière étape, l'utilisation de <kbd>drupal_to_js</kbd> qui va transformer ce code XHTML en un fragment au format 
  <a target='_blank' href='http://fr.wikipedia.org/wiki/JSON'>
  JSON
  </a> que le module AHAH du client est capable de comprendre. Une fois cette dernière transformation faite, le tout est simplement envoyé au client. 
</p>
<p>
  Notez la fonction <kbd>exit()</kbd> qui arrête le traitement ici, interdisant à Drupal tout autre opération. 
</p>


	<a name='chapter_6'></a>
  <h2>Conclusion</h2>
	
<p>
  Voilà, c'est tout et ça marche très bien. Ne vous laissez pas trop effrayer par l'apparente complexité de l'approche car je l'ai développée au maximum. Il est possible de créer une ou deux fonctions génériques qui permettraient de faire la même chose en quelques lignes. Si cela continue dans cette voie, on va finir par pouvoir faire des choses aussi basiques que celles-ci avec la même simplicité que les Delphi & co d'il y a 10 ans... Enfin, je rêve un peu, avec les client Riches, il a fort à parier que tout cet acquis soit à nouveau remis en jeu...
</p>
    ]]></content>
  </entry>
</feed>
