<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>La Case de l&#039;Oncle Tom &#187; ajax</title>
	<atom:link href="http://case.oncle-tom.net/tag/ajax/feed/" rel="self" type="application/rss+xml" />
	<link>http://case.oncle-tom.net</link>
	<description>Développement Web, bonnes pratiques et performances</description>
	<lastBuildDate>Sun, 25 Dec 2011 19:33:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<atom:link rel="search"
           href="http://case.oncle-tom.net/opensearch"
           type="application/opensearchdescription+xml"
           title="Content Search" />		<item>
		<title>Tableau auto-extensible en JavaScript</title>
		<link>http://case.oncle-tom.net/2007/tableau-auto-extensible-en-javascript/</link>
		<comments>http://case.oncle-tom.net/2007/tableau-auto-extensible-en-javascript/#comments</comments>
		<pubDate>Tue, 26 Jun 2007 11:59:32 +0000</pubDate>
		<dc:creator>Oncle Tom</dc:creator>
				<category><![CDATA[Accessibilité]]></category>
		<category><![CDATA[Développement Web]]></category>
		<category><![CDATA[Standards du Web]]></category>
		<category><![CDATA[ajax]]></category>
		<category><![CDATA[bonne pratique]]></category>
		<category><![CDATA[code]]></category>
		<category><![CDATA[dom]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[mootools]]></category>
		<guid isPermaLink="false">http://case.oncle-tom.net/2007/06/26/tableau-auto-extensible-en-javascript/</guid>
		<description><![CDATA[Bon nombre de clients utilisant les sites Internet de mon employeur passent des commandes en saisissant directement les codes articles de leur choix. Ce n&#8217;est pas anodin : pour du réassort de magasin, ils savent exactement ce qu&#8217;ils veulent. Et un beau jour on vient me voir en me disant qu&#8217;ils ne peuvent saisir qu&#8217;un [...]]]></description>
			<content:encoded><![CDATA[<p>Bon nombre de clients utilisant les sites Internet de mon employeur passent des commandes en saisissant directement les codes articles de leur choix. Ce n&#8217;est pas anodin : pour du réassort de magasin, ils savent exactement ce qu&#8217;ils veulent. Et un beau jour on vient me voir en me disant qu&#8217;<cite>ils ne peuvent saisir qu&#8217;un code et une seule quantité à la fois</cite>, qu&#8217;il faudrait changer ça.</p>
<p style="text-align:center"><img src="http://case.oncle-tom.net/images/2007/06/articles-autoexpand-mini.png" alt="Ligne article" /></p>
<p>Voici donc mon constat de départ :</p>
<ul>
<li>un client doit pouvoir saisir <em>un seul article</em> s&#8217;il le veut</li>
<li>un client doit pouvoir saisir <em>10 articles</em> s&#8217;il le veut</li>
<li>un client doit pouvoir saisir <em>100 articles</em> s&#8217;il le veut</li>
<li>mais <strong>celui qui n&#8217;en saisit que 10 ne doit pas être gêné par 100 lignes de formulaire à remplir</strong></li>
<li>un <em>débutant</em> doit pouvoir saisir 100 lignes sans avoir de mode d&#8217;emploi sous la main</li>
</ul>
<p>Donc ma solution a été très simple : <strong>je ne mettrai qu&#8217;une ligne</strong>.<br />
<span id="more-680"></span></p>
<h3>L&#8217;idée</h3>
<p>Comment faire pour satisfaire tout le monde, les gros donneurs d&#8217;ordre comme les petits ?<br />
<strong>En faisant en sorte que le formulaire s&#8217;adapte à leur besoin.</strong> Je voulais donc qu&#8217;à chaque ligne complétée s&#8217;en ajoute une autre avant de passer à la validation finale. Place au combo <strong>JavaScript + <acronym title="Document Object Model">DOM</acronym> = Ajax</strong>.</p>
<h3>La version dégradée</h3>
<p>Une <em>bonne pratique de l&#8217;Ajax</em> est de partir du cas particulier : celui où les conditions ne sont pas requises. En clair, une personne qui n&#8217;a pas JavaScript d&#8217;activé ou pas de JavaScript tout court.<br />
Conclusion, j&#8217;ai décidé de générer un tableau en <acronym title="HyperText Markup Language">HTML</acronym> de 10 lignes. 10 est un choix arbitraire, modifiable à tout moment et qui ne doit donc pas gêner la version dynamique en cas de changement (ajout de colonne, ajout de lignes).</p>
<p style="text-align:center"><img src="http://case.oncle-tom.net/images/2007/06/articles-autoexpand.png" alt="Tableau auto-extansible dégradé" /></p>
<h3>La version Ajax</h3>
<p>On peut manipuler le document à loisir grâce à JavaScript. <strong>Avec un code propre et ordonné c&#8217;est d&#8217;autant plus simple</strong>. Alors voici la logique à mettre en oeuvre :</p>
<ol>
<li>éliminer toutes les lignes dont on ne veut pas et n&#8217;en garder qu&#8217;une</li>
<li>assigner à chaque champ de formulaire de la ligne (<code>input</code>, <code>select</code>, <code>textarea</code>) une fonction de vérification : si tous les éléments de la ligne sont remplis / cochés et qu&#8217;il n&#8217;y a pas de ligne après, on clonera la ligne</li>
<li>cloner la ligne en la vidant de ses valeurs, en modifiant les noms de champ pour les rendre exploitable après coup</li>
</ol>
<p><strong><br />
Ce qu&#8217;on l&#8217;on veut étant relativement simple, <em>le code doit l&#8217;être également</em></strong> ! Le résultat aussi, la preuve en mouvement.</p>
<div style="text-align:center"><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="372" height="188" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="src" value="http://case.oncle-tom.net/images/2007/06/articles-autoexpand.swf" /><embed type="application/x-shockwave-flash" width="372" height="188" src="http://case.oncle-tom.net/images/2007/06/articles-autoexpand.swf"></embed></object></div>
<p>Au final, <strong>50 lignes de JavaScript ont suffit</strong> à transformer un tableau complètement statique en tableau dynamique, s&#8217;adaptant à la demande de n&#8217;importe quel client. Client qui pourra également utiliser le clavier pour davantage d&#8217;efficacité de saisie.</p>
<p>C&#8217;est à dire <strong>exactement ce que l&#8217;on souhaitait au départ</strong>.</p>
<h3>Le code</h3>
<h4>Le tableau <acronym title="HyperText Markup Language">HTML</acronym></h4>
<pre><code class="html">&lt;table cellspacing="0" id="ajout_multi_articles"&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Code article&lt;/th&gt;
      &lt;th&gt;Quantité&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[1][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[1][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[2][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[2][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[3][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[3][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[4][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[4][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[5][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[5][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[6][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[6][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[7][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[7][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[8][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[8][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[9][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[9][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr class="article"&gt;
      &lt;th&gt;&lt;input type="text" name="article[10][ref]" maxlength="10" /&gt;&lt;/th&gt;
      &lt;td&gt;&lt;input type="text" name="article[10][qte]" maxlength="5" size="5" /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</code></pre>
<h4>La fonction JavaScript</h4>
<pre><code class="javascript">/**
 * Transforme un tableau multi-lignes en un tableau mono-ligne mais auto-extensible
 * Renomme également les noms de champ en suivant un masque paramétrable ; par défaut article[]
 *
 * @usage articlesMultiRows([{'css_id': ?, 'css_row_class': ?, 'check_inputs': ?}])
 * @param {Object} params Objet contenant (ou pas) les paramètres de contrôle de la classe
 */
function articlesMultiRows(params)
{
  this.row_number = 0;
  /*
   * Extension des paramètres par défaut
   */
  var params = params || {};
  params.css_id = params.css_id || 'ajout_multi_articles';
  params.css_row_class = params.css_row_class || 'article';
  params.check_inputs = params.check_inputs || 'input,select,textarea';
  /**
   * Initialise le tableau de données et accroche les méthodes dynamiques
   */
  this.init = function(){
    //aucun ID détecté, pas la peine de continuer plus loin
    if (!$(params.css_id))
    {
      return false;
    }
    //on récupère les lignes, on enlève la première du tableau et on supprime les autres
    var rows = $(params.css_id).getElements('tr.'+params.css_row_class);
    var row_first = rows.shift();
    rows.removeElements();
    rows = null;
    /*
     * On assigne l'évènement aux champs paramétrés de la ligne
     * On place le focus sur le premier champ
     */
    $ES(params.check_inputs, row_first).addEvent('blur', rowCheck);
    $(row_first).getElement('input').focus();
    row_number = 1;
    /*
     * On écrit un petit message sympathique indiquant le fonctionnement du bazar
     * On le fait via JavaScript car un utilisateur sans <acronym title="JavaScript">JS</acronym> ne pourrait justement pas suivre les infos
     * Il est placé juste avant le tableau
     */
    new Element('p',{
          'class' : 'message'
          }).setHTML("Dès qu'un code article et une quantité sont saisis ci-dessous, &lt;strong&gt;une nouvelle ligne s'ajoute automatiquement&lt;/strong&gt;.&lt;br /&gt;Afin d'accélérer votre saisie, &lt;em&gt;utilisez la touche de tabulation de votre clavier&lt;/em&gt; pour passer d'un champ à l'autre. Vous verrez, c'est facile et surtout efficace !").injectBefore(params.css_id);
  };
  /**
   * Vérifie si la ligne a entièrement été complétée et le cas échéant, déclenche l'ajout de ligne
   */
  this.rowCheck = function(){
    var row = { 'inputs' : 0, 'completed' : 0, 'dom' : $(this).getParent().getParent() };
    $ES(params.check_inputs, row.dom).each(function(input){
      //Un élément de plus ...
      ++row.inputs;
      //... dont on vérifie s'il est bien complété
      switch (input.getTag())
      {
        case 'input':
        case 'textarea':
          if (input.value.clean())
          {
            ++row.completed;
          }
        break;
        case 'select':
          if (input.selectedIndex &gt;= 0 &amp;&amp; input.selectedIndex &lt; input.length)
          {
            ++row.completed;
          }
        break;
      }
    });
    /*
     * Pour insérer une nouvelle ligne, on doit avoir :
     * - autant de champs complétés que de champs existants
     * - aucun élément suivant
     */
    if (row.inputs === row.completed &amp;&amp; !row.dom.getNext())
    {
      rowInsert(row.dom);
    }
  };
  /**
   * Insère une nouvelle ligne en clonant la dernière du tableau et la purgeant de ses valeurs
   *
   * @param {Object} row Noeud <acronym title="Document Object Model">DOM</acronym> correspondant à la ligne à cloner
   */
  this.rowInsert = function( row ){
    //On incrémente le nombre de lignes dispo
    //Ce numéro sert à changer les masques de nom de champ
    ++row_number;
    var row_clone = row.clone();
    //Clonage de la ligne
    $ES('input[type=text],input[type=password],input[type=hidden],textarea', row_clone).setProperty('value', '');
    $ES(params.check_inputs, row_clone).removeEvents('blur'); /* obligé car <acronym title="Internet Explorer 5">IE5</acronym>+ clone aussi les évènements ... alors qu'il ne le devrait pas */
    $ES(params.check_inputs, row_clone).addEvent('blur', rowCheck); /* oui oui, <acronym title="Internet Explorer 5">IE5</acronym>+ ne rassignait pas cet évènement comme il fallait malgré ça */
    //On remplace les noms des champs pour que permettre une utilisation des résultats $_POST
    $ES(params.check_inputs, row_clone).each(function(input){
      input.name = input.name.replace(new RegExp('^'+params.css_row_class+'\[[0-9]+\]'), params.css_row_class+'['+row_number+']');
    });
    //Injection de la ligne, une fois tout le travail terminé
    row_clone.injectAfter(row);
    //Nettoyage, le clonage, ça salit
    row_clone = null;
    row = null;
  };
  /*
   * Initialisation de la classe
   */
  this.init();
}
/*
 * Initialisation du tableau multi-lignes avec les options par défaut
 */
window.addEvent('domready', articlesMultiRows);</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://case.oncle-tom.net/2007/tableau-auto-extensible-en-javascript/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
	</channel>
</rss>

