Première étape : construire la page

Tout d'abord, il faut faire la page web. Pour cela, on utilise du XHTML tout à fait classique, et on construit un tableau. Je vous invite à lire l'article Habillage de tableaux avec des CSS sur Openweb pour en savoir plus sur la manière de faire un tableau conforme aux standards.

Le tableau que j'ai choisi de dessiner est on-ne-peut-plus simple : il contient 4 colonnes contenants chacunes un champ de type "text", et une dernière colonnes contenant les actions possibles. Pour l'instant, la seule action possible est la suppression d'une ligne.

Le code donne (vous pouvez consulter l'exemple 1) :

<table class="dTable">
        <thead>
                <tr>
                        <th>Colonne 1</th>
                        <th>Colonne 2</th>
                        <th>Colonne 3</th>
                        <th>Colonne 4</th>
                        <th>Actions</th>
                </tr>
        </thead>
        
        <tfoot>
                <tr>
                        <th colspan="5"><a href="#">Ajouter une ligne</a></th>
                </tr>
        </tfoot>
        
        <tbody>
                
                <tr>
                        <td><input type="text" name="champ1[]" /></td>
                        <td><input type="text" name="champ2[]" /></td>
                        <td><input type="text" name="champ3[]" /></td>
                        <td><input type="text" name="champ4[]" /></td>
                        <td><a href="#">Supp</a></td>
                </tr>
                
        </tbody>
</table>

Les liens ont tous pour trajet (href) la valeur #. Cela revient à dire qu'ils n'ont pas d'effet... pour l'instant. J'ai appliqué la classe dTable au tableau (comme « Dynamic Table ») afin de pouvoir l'identifier parmis d'autres eventuels tableaux, qui, eux, ne seront pas dynamiques. Enfin, dernier détails, les champs input ont tous un nom finissant par []. Nous verrons dans la troisième partie à quoi ça va servir.

Deuxième étape : rendre le tableau dynamique

Maintenant on rentre dans le vif du sujet. Pour rendre ce tableau dynamique, on va utiliser du Javascript et du DOM, en s'arrageant pour qu'il soit compatible Internet Explorer et Firefox.

Première chose : inclure un script. Pour cela, on rajoute dans le header de la page html la ligne suivante (avec dtable.js le nom du fichier script) :

<script type="text/javascript" src="dtable.js"></script>

Bien ensuite, un peu de conception... Comment allons nous procéder ?

Ajout

Pour ajouter une ligne, le plus simple est d'utiliser la méthode DOM cloneNode() qui permet de dupliquer un noeud (par exemple un tr, donc une ligne du tableau) et appendChild() qui permet d'ajouter un noeud à un autre (par exemple le tr cloné au tbody, donc au corps du tableau). Cette méthode est très simple à condition d'avoir une ligne « de référence », c'est à dire une ligne vide que l'on pourra cloner pour simuler l'ajout de ligne. La première ligne du tableau (la seule que j'ai saisis dans le code html ci-dessus) fera office de référence.

Nous allons faire une fonction addLigne qui prend en paramètre l'élément appellant (en l'occurence il s'agira d'un lien, donc d'un element A). A partir du lien, il faudra retrouver le tableau en « remontant » dans l'arbre html à la recherche du parent s'appelant TABLE. A partir du tableau, on pourra retrouver le TBODY et la première ligne, et effectuer l'ajout.

Pour retrouver le parent d'un élément, j'utilise une fonction récursive, getParent(element, parentTagName), dont voici l'implémentation :

/* trouve le tag "parentTagName" parent de "element" */
function getParent(element, parentTagName) {
        if ( ! element )
                return null;
        else if ( element.nodeType == 1 && element.tagName.toLowerCase() == parentTagName.toLowerCase() )
                return element;
        else
                return getParent(element.parentNode, parentTagName);
}

Le code pour ajouter une ligne devient :

/* ajoute une ligne */
function addLigne(link) {
        // 1. récuperer le node "TABLE" à manipuler
        var td = link.parentNode;
        var table = getParent(td,'TABLE');
        
        // 2. on va manipuler le TBODY
        var tbody = table.tBodies[0];
        
        // 3. on clone la ligne de reference
        var newTr = tbody.rows[0].cloneNode(true);
        tbody.appendChild(newTr);
}

Pour appeller cette fonction, on peut transformer le lien « Ajouter une ligne » comme ceci :

<a href="#" onclick="addLigne(this); return false;">

Le return false; permet de stopper le traitement normal du lien et donc de ne pas executer le href.

Suppression

Là encore, une fonction DOM nous mache tout le travail : removeChild(). Il suffit de trouver le TR à supprimer en remontant l'arbre html depuis le lien appellant, et de le retirer du TBODY

/* supprimer une ligne */
function delLigne(link) {
        // 1. récuperer le node "TABLE" à manipuler
        var td = link.parentNode;
        var table = getParent(td, 'TABLE');
        
        // 2. récuperer le TBODY
        var tbody = table.tBodies[0];
        
        // 3. Supprimer le TR
        tbody.removeChild(getParent(td, 'TR'));
}

Pareil que pour l'ajout, on peut transformer les liens « Supp » comme ceci :

<a href="#" onclick="delLigne(this); return false;">

L'ajout et la suppression donnent l'exemple 2.

Protéger la première ligne

Le problème de l'exemple 2, que vous avez peut-être remarqué en testant, est que la première ligne, la ligne de référence donc, est supprimable comme les autres. Et lorsqu'on supprime tous les lignes, il n'y en a plus aucune à cloner ! On va donc masquer la première ligne avec la propriété CSS display="none" et en ajoute une vide supplémentaire.

On ajoute au code :

window.onload = dtableInit;

/* initialise le script */
function dtableInit() {
        var table = document.getElementsByTagName('TABLE');
        for ( var i = 0; i < table.length; i++ ) {
                // on récupère tous les tableaux dynamiques
                if ( table[i].className == 'dTable' ) {
                        
                        var tbody = table[i].tBodies[0];
                        var newTr = tbody.rows[0].cloneNode(true);
                        
                        // on masque la première ligne du tbody (la ligne de reference)
                        tbody.rows[0].style.display = 'none';
                        
                        // on en ajoute une
                        tbody.appendChild(newTr);
                        
                }
        }
}

Un dernier problème subsiste : lorsqu'on clone cette ligne, son style est cloné également. Toutes les lignes sont donc invisibles ! Il faut ajouter à la fin de addLigne de quoi les remettres visibles :

        if ( document.all )
                newTr.style.display = "block"; // pour IE
        else
                newTr.style.display = "table-row"; // pour Gecko

L'utilisation de document.all permet de détecter s'il s'agit d'IE ou pas. En effet, seul ce navigateur implémente cette propriété (non-standard).

Vous pouvez tester ce code dans l'exemple 3.

Troisième étape : traiter les données

Grace à leur nom finissant par [], les valeurs des champs input seront assemblées dans un tableau lors du post du formulaire. Résultat : on se retrouve avec 4 tableaux : champ1, champ2, champ3 et champ4 correspondants aux 4 colonnes du tableaux html. Les lignes de ces tableaux correspondent aux lignes du tableau html (en commençant par 0).

Imaginons qu'il faille mettre à jour une base, le plus rapide est de tout supprimer puis de réinsérer les données directement ! Pas besoin de s'embêter à trouver ce qui a changé puisque tout est renvoyé.

Dernière chose : au moment de traiter ces données, il ne faut pas oublier d'oublier l'index 0, qui correspond à la ligne de référence.

Vous pouvez tester le résultat renvoyé par le serveur grace à l'exemple 4.

Conclusion

Rajouter des fonctions à ce script est extrement simple : il suffit de veiller à ne pas toucher à la première ligne. On peut par exemple imaginer des fonctions permettant de monter une ligne, de la descendre, de la dupliquer, etc. Bref, les possibilités de créer des interfaces web se rapprochant de celles des clients lourds sont de plus en plus nombreuses et de plus en plus simples à mettre en oeuvre grace notamment aux standards du W3C !