Générer une table des matières à l'aide du Document Object Model et JavaScript

Lorsque vous écrivez un article et que vous structurez votre document à l'aide des balises d'en-tête h1 à h6, il peut vous être utile d'y insérer une table des matières pointant sur les titres correspondants afin de faciliter la consultation de votre chef-d'œuvre. Plusieurs choix vous sont possibles. Soit vous mettez en place vous-même la table des matières, ce qui impliquera certainement des erreurs lors des diverses mises à jour. Soit vous utilisez un parseur grâce à un langage côté serveur, mais analyser un document coûte cher en termes de ressources. La solution retenue pour cet article est la génération du sommaire chez le client : le visiteur. La table des matières sera donc générée grâce à JavaScript et aux fonctions du DOM. À chaque titre hn correspondra une ligne dans la table des matières. Un clic sur cette ligne renverra bien entendu à l'endroit où figure ledit titre. Pour les impatients voici le résultat du script table des matières (JavaScript doit être activé bien sûr).

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Petit rappel à propos des fonctions utilisées

Tous les accès aux éléments HTML sont faits grâce aux fonctions du DOM. Toutes les modifications apportées à l'arbre grammatical du document HTML sont réalisées grâce aux fonctions du DOM. Cette liste n'est pas exhaustive, mais indique les fonctions et propriétés les plus intéressantes.

getElementById()

  • Cette fonction permet d'accéder directement à un élément par son identifiant id. Il est ensuite possible de manipuler cet élément : lui ajouter un événement, changer les propriétés de style, modifier son contenu…

childNodes

  • Cette propriété retourne un tableau contenant la liste des nœuds enfants de l'élément depuis lequel est appelée la fonction. Ces nœuds peuvent aussi bien être du texte brut, qu'un autre élément HTML. Attention toutefois, certains navigateurs, notamment Netscape 6.1, Firefox, et certaines versions d'Internet Explorer sous Macintosh interprètent les espaces et sauts de ligne entre différents éléments comme étant des nœuds de type #text. Ce bogue connu, n'est valable que dans le code HTML lui-même, et non dans le code HTML généré grâce au DOM. Il faudra donc effectuer certaines vérifications lors de la manipulation de la source du document à l'aide de la fonction nodeName.

createElement()

  • Cette fonction crée un nouvel élément dans l'arborescence. Cet élément n'est pas visible tant qu'il n'est pas rattaché à l'arborescence du document. L'élément existe par exemple au sein d'une variable jusqu'à ce qu'il soit placé dans le document. La création d'éléments ne se limite pas qu'au HTML, il est en effet possible de les créer dans un document XML.

createTextNode()

  • Cette fonction crée un nouveau nœud de type texte #text. Ce n'est pas un élément, c'est du texte brut : le nœud de plus bas niveau. Ce texte n'est pas visible tant qu'il n'est pas inséré dans l'arborescence du document. Le texte existe par exemple au sein d'une variable jusqu'à ce qu'il soit placé dans le document. La création de nœuds de type texte ne se limite pas qu'au HTML, il est en effet possible de les créer dans un document XML.

appendChild()

  • Cette fonction ajoute un nœud à l'intérieur d'un nœud donné. Il est inséré à la suite des nœuds déjà présents à l'intérieur. C'est une des fonctions qui permettent de rattacher un élément dans l'arborescence du document.

nodeName

  • Cette propriété retourne le nom de l'élément. C'est le nom de la balise : h1, body… Un nœud de type texte renverra la valeur #text. C'est notamment avec cette méthode que l'on pourra effectuer des vérifications pour contourner le bogue d'interprétation de childNodes.

getAttribute()

  • Cette fonction retourne la valeur assignée à un attribut dans un élément. Elle ne retourne aucune valeur si l'attribut n'existe pas dans l'élément.

id

  • Cette propriété retourne l'identifiant assigné à un élément. Si cet élément ne possède pas d'identifiant, la propriété ne contient rien. Cette propriété est utilisable aussi bien en lecture qu'en écriture.

className

  • Cette propriété retourne le ou les noms de classe class de l'élément, séparés par des espaces. Si la classe est vide, aucune valeur n'est retournée. Cette propriété est utilisable en lecture comme en écriture.

insertBefore()

  • Cette fonction permet d'insérer un nœud dans le nœud depuis lequel est exécutée la fonction, juste avant le nœud précisé en paramètre. Le nœud nouvellement inséré est alors le frère du nœud passé en paramètre.

firstChild

  • Cette propriété retourne le premier nœud enfant du nœud depuis lequel elle est appelée. Étant donné le bogue concernant childNodes, il vaut mieux éviter d'utiliser cette propriété dans le code source directement. Elle peut être utilisée sur des objets déjà créés par le DOM, mais pas sur ceux déjà présents dans la page sous peine d'erreurs.

lastChild

  • Cette propriété retourne le dernier nœud enfant du nœud depuis lequel elle est appelée. Étant donné le bogue concernant childNodes, il vaut mieux éviter d'utiliser cette propriété dans le code source directement. Elle peut être utilisée sur des objets déjà créés par le DOM, mais pas sur ceux déjà présents dans la page sous peine d'erreurs.

parentNode

  • Cette propriété retourne le nœud parent d'un nœud donné. En d'autres termes, elle retourne le nom de l'élément dans lequel est contenu un élément donné.

style

  • Cette propriété permet d'accéder directement aux règles CSS de l'élément concerné. Il est possible de modifier les propriétés CSS telles que couleur du texte, arrière-plan, largeur, visibilité…

II. Contexte d'utilisation

Il est inutile de préciser que votre document (x)HTML devra posséder quelques titres avec ou sans identifiant id. Une fois la table des matières générée, un clic sur celle-ci vous mènera à la section correspondante de la page. Voici donc la structure que nous allons utiliser au cours de cet article. Il s'agit du plan de mon rapport de stage effectué à la société Solvay France. Il faut bien sûr s'imaginer que tous ces titres ne tiennent pas sur une seule page. Afin de bien mettre en évidence les capacités du DOM, certaines parties du document sont isolées dans des balises div. Voyez le résultat du code suivant dans l'exemple 1 (quelques règles de style ont été ajoutées afin de bien visualiser la structure).

 
Sélectionnez
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
 <head>
  <title>données pour table des matières</title>
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <meta http-equiv="Content-Language" content="fr" />
 </head>
 <body id="top">
  <h1>Gestion des Emplois Vacances</h1>
   <h2 id="remerciements">Remerciements</h2>
    <p>...</p>
   <h2 id="introduction">Introduction</h2>
    <p>...</p>
   <div id="formalites">
    <h2>Présentation du groupe Solvay</h2>
     <p>...</p>
     <h3>Le groupe Solvay en France</h3>
      <p>...</p>
     <h3>L'usine Solvay de Tavaux</h3>
      <p>...</p>
      <h4>Evolution</h4>
       <p>...</p>
      <h4>Activités et productions</h4>
       <p>...</p>
      <h4>Situation géographique</h4>
       <p>...</p>
      <h4>La structure de l'entreprise</h4>
       <p>...</p>
     <h3>Le matériel informatique de Solvay Tavaux</h3>
      <p>...</p>
      <h4>Infrastructure</h4>
       <p>...</p>
      <h4>Le réseau local</h4>
       <p>...</p>
   </div>
   <div id="sujet">
    <h2>Présentation du sujet du stage</h2>
     <p>...</p>
     <h3 id="contexte">Définition du contexte</h3>
      <p>...</p>
     <h3>Situation initiale</h3>
      <p>...</p>
     <h3>Les problèmes rencontrés</h3>
      <p>...</p>
     <h3>La solution retenue</h3>
      <p>...</p>
     <h3>Les composants logiciels client serveur</h3>
      <p>...</p>
      <h4 id="serveurweb">Le serveur web</h4>
       <p>...</p>
      <h4>La base de données</h4>
       <p>...</p>
     <h3>Structure de données d'Emplois Vacances</h3>
      <p>...</p>
      <h4>Les entités déjà existantes</h4>
       <p>...</p>
      <h4>Les entités ajoutées à la base</h4>
       <p>...</p>
     <h3>Les différentes étapes du travail</h3>
      <p>...</p>
      <h4 id="controleacces">Le contrôle des accès</h4>
       <p>...</p>
      <h4>Mise en place de la structure de la page</h4>
       <p>...</p>
      <h4>Déjouer les problèmes de structure de données</h4>
       <p>...</p>
      <h4>Création d'une demande d'emploi vacances</h4>
       <p>...</p>
      <h4>Impression de la liste des postulants</h4>
       <p>...</p>
      <h4>Choix des stagiaires et saisie des contrats</h4>
       <p>...</p>
      <h4>Personnalisation des courriers</h4>
       <p>...</p>
      <h4>Protection des requêtes SQL</h4>
       <p>...</p>
      <h4>Consultation de l'historique</h4>
       <p>...</p>
   </div>
  <h2 id="conclusion">Conclusion</h2>
   <p>...</p>
 </body>
 </html>

La table des matières devra mettre en évidence la hiérarchie des éléments de titre grâce à une indentation (marge gauche). Les ancres seront réalisées à l'aide des attributs id. Si l'identifiant existe, on l'utilisera pour créer le lien, dans le cas contraire, on en générera un, unique dans la page. La démonstration sera faite en plusieurs étapes accompagnées d'explications.

III. Le script Table des matières

Ce script est fourni sous Licence GPL. Vous pouvez donc l'utiliser librement, le modifier à condition de respecter ladite licence. Merci de m'informer si vous découvrez une incompatibilité avec un navigateur, un bogue, ou pour toute suggestion d'amélioration. Voici donc le script. Les commentaires ont été supprimés pour ne pas alourdir la page, mais vous pouvez télécharger la version commentée.

 
Sélectionnez
<script type="text/javascript">

  /*
   * Script table des matières. Ce script est publié sous licence GPL
   * article : http://giminik.developpez.com/articles/javascript-dom/table-des-matieres/
   * date : 2005-09-14
   * http://www.gnu.org/copyleft/gpl.html
   * Vous pouvez le modifier librement et le redistribuer.
   * Merci de m'indiquer tout bogue, incompatibilité, amélioration 
   * à giminik   at   redaction-developpez.com
   */

  /* Cette fonction permet d'afficher/cacher le contenu d'un élément dont on
   * connaît l'identifiant : containerId. Ici, on s'en sert pour cacher la 
   * liste de liens. En même temps, le nom de classe de l'élément titre de
   * la liste est modifiée afin de pouvoir lui affecter un style CSS.
   * containerId : l'identifiant de l'élément html à afficher caché.
   * classOpened : le nom de la classe à donner à l'élément html lorsqu'il est
   *               affiché.
   * classClosed : le nom de la classe à donner à l'élément html lorsqu'il est 
   *               caché.
   */ 
  function TCSwap(containerId, classOpened, classClosed) {

    var linkList = document.getElementById(containerId).lastChild;
    var listTitle = document.getElementById(containerId).firstChild;

    if (linkList.style.display != 'none') {

      linkList.style.display = 'none';

      listTitle.className = classClosed;

    }
    else if (linkList.style.display != 'block') {

      linkList.style.display = 'block';

      listTitle.className = classOpened;

    }

  }


  /* Cette fonction génère la table des matières. Elle construit les éléments
   * html et les insère dans l'arborescence du document.
   *
   * contentId : seuls les titres contenus dans l'élément (et ses sous-éléments)
   *             ayant comme id contentId seront utilisés pour la table des matières.
   *             ce doit être un identifiant valide et existant.
   * insertBeforeId : la table des matières sera insérée juste avant l'élément
   *                  portant cet identifiant. ce doit être un identifiant
   *                  valide et existant.
   * containerId : le nom du conteneur sera celui passé en paramètre. cet
   *               identifiant ne doit pas déjà être utilisé dans la page.
   * minHead : par exemple 5 pour titre h5 : les titres hiérarchiquement inférieurs
   *           sont ignorés. Doit être compris entre 1 et 6.
   * maxHead : par exemple 2 pour titre h2 : les titres hiérarchiquement supérieurs
   *           sont ignorés. Doit être compris entre 1 et 6 et doit être inférieur
   *           à minHead.
   * tableHeadLevel : un titre est inséré pour annoncer la table des matières.
   *                  utilisez 3 pour que le titre de cette table des matières soit
   *                  h3. doit être compris entre 1 et 6.
   * clickable : booléen indique si la table des matières est rétractable sur
   *             l'événement click. Doit prendre comme valeur true ou false.
   */
  function contentTable(contentId, insertBeforeId, containerId, minHead,
                        maxHead, tableHeadLevel, clickable) {

    var contentTableTitle = 'Table des matières';
    var anchorName = 'tableDesMatieres';
    var anchorsNumberingBeginning = 0;
    var openedClass = 'ouvert';
    var closedClass = 'ferme';


    if (!document.getElementById) return;


    if (!minHead || minHead < 1 || minHead > 6) {
      minHead = 6;
    }

    if (!maxHead || maxHead < 1 || maxHead > minHead) {
      maxHead = 1;
    }

    if (!tableHeadLevel || tableHeadLevel < 1 || tableHeadLevel > 6) {
      tableHeadLevel = 2;
    }


    if (document.getElementById(containerId)) {

      alert(containerId + ' already exists in this page!');
      return;

    }
    else if (!document.getElementById(insertBeforeId)) {

      alert(insertBeforeId + ' is not an existing id!');
      return;

    }
    else if (!document.getElementById(contentId)) {

      alert(contentId + ' is not an existing id!');
      return;

    }
    else {

      var TCContainer = document.createElement('div');

      TCContainer.id = containerId;

      var content = document.getElementById(contentId);

      var chapters = Array();

      headTag(content, chapters);

      if (chapters.length < 2) return;

      var TCTitle = document.createElement('h' + tableHeadLevel);
      TCTitle.appendChild(document.createTextNode(contentTableTitle));

      TCContainer.appendChild(TCTitle);


      var theList = document.createElement('ul');


      if (clickable) {

        TCTitle.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

        TCTitle.className = openedClass;

        theList.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

      }

      for (var i = 0; i < chapters.length; i++) {

        var titleNumber = parseInt(chapters[i].nodeName.charAt(1));


        if (titleNumber <= minHead && titleNumber >= maxHead) {

          var anItem = document.createElement('li');

          var aLink = document.createElement('a');
          aLink.appendChild(document.createTextNode(inText(chapters[i])));

          anItem.className = chapters[i].nodeName.toLowerCase();


          if (chapters[i].id) {

            aLink.href = '#' + chapters[i].id;

          }
          else {

            do {

              anchorsNumberingBeginning++;

            } while (document.getElementById(anchorName + anchorsNumberingBeginning))


            chapters[i].id = anchorName + anchorsNumberingBeginning;

            aLink.href = '#' + chapters[i].id;

          }

          anItem.appendChild(aLink);

          theList.appendChild(anItem);

        }

      }

      TCContainer.appendChild(theList);

      var beforeElement = document.getElementById(insertBeforeId);

      var theParent = beforeElement.parentNode;

      theParent.insertBefore(TCContainer, beforeElement);

    }

  }




  /* Cette fonction ajoute récursivement la liste des balises d'en-têtes à l'intérieur 
   * d'un nœud dans le tableau passé en paramètres. Afin de conserver l'ordre et de 
   * prendre en compte tous les éléments d'un nœud, cette fonction est récursive.
   * node : Il s'agit du nœud dans lequel on recherche les éléments titre.
   * headArray : Il s'agit du tableau dans lequel on va ajouter les nœuds
   *             des éléments titre Hn.
   */
  function headTag(node, headArray) {

    var childrenNumber = node.childNodes.length;

    for (var i = 0; i < childrenNumber; i++) {

      var element = node.childNodes[i];

      var elementName = element.nodeName.toLowerCase();

      if (elementName == 'h1' || elementName == 'h2' || elementName == 'h3' 
                              || elementName == 'h4' || elementName == 'h5' 
                              || elementName == 'h6') {

        headArray[headArray.length] = element;

      }
      else {

        headTag(element, headArray);

      }

    }

  }


  /* Cette fonction retourne le texte contenu dans un nœud, uniquement le texte.
   * Le texte est épuré de toutes les balises intermédiaires.
   * node : le nœud pour lequel on ne souhaite récupérer que la partie textuelle.
   */
  function inText(node) {

    var childrenNumber = node.childNodes.length;

    var foundString = "";

    if (childrenNumber == 0) {
      return node.nodeValue;
    }
    else { 

      for (var i = 0; i < childrenNumber; i++) {

        foundString += inText(node.childNodes[i]);

      }

      return foundString;

    }

  }

</script>

Ce script est compatible avec les navigateurs suivants (liste non exhaustive) :
Je n'ai pas pu tester avec de vieilles versions des navigateurs cités ci-dessous, merci de m'indiquer si une version plus ancienne est compatible avec ce script.

  • Internet Explorer 5 et supérieur
  • Mozilla 1.6 et supérieur
  • Firefox 1.0 et supérieur
  • Opéra 8.0 et supérieur
  • Netscape 6 et supérieur

IV. Explications

La difficulté principale fut de pouvoir récupérer la liste complète des éléments titres au sein du document. L'utilisation de la fonction getElementsByTagName aurait été très utile, seulement il aurait été impossible ensuite de connaître l'ordre dans lequel les éléments interviennent dans le document. En effet, cette fonction n'accepte qu'un seul paramètre : il aurait alors fallu récupérer tous les titres H1, puis tous les titres H2… On obtient alors plusieurs tableaux de nœuds sans aucune relation entre eux. La vraie solution consiste donc à utiliser une fonction récursive qui descend du nœud racine jusqu'aux nœuds enfants les plus bas dans la hiérarchie. Chaque nœud titre est alors stocké dans un tableau. L'ordre est donc conservé.

IV-A. Accès à tous les titres contenus dans un nœud

 
Sélectionnez
  function headTag(node, headArray) {

    var childrenNumber = node.childNodes.length;

    for (var i = 0; i < childrenNumber; i++) {

      var element = node.childNodes[i];

      var elementName = element.nodeName.toLowerCase();

      if (elementName == 'h1' || elementName == 'h2' || elementName == 'h3' 
                              || elementName == 'h4' || elementName == 'h5' 
                              || elementName == 'h6') {

        headArray[headArray.length] = element;

      }
      else {

        headTag(element, headArray);

      }

    }

  }

Lors de l'appel de la fonction, on doit passer en argument le nœud dans lequel on recherche les titres H1 à H6 et un tableau préalablement déclaré. La fonction ne retourne rien, elle ajoute juste au tableau les nœuds de type titre Hn. En premier lieu, on compte le nombre de nœuds enfants du nœud passé en paramètre à l'aide de l'instruction var childrenNumber = node.childNodes.length;. Ensuite, pour chaque enfant for (var i = 0; i < childrenNumber; i++) {, on teste son nom d'élément. S'il s'agit d'un élément H1, H2, H3, H4, H5 ou H6, on empile ce nœud dans le tableau headArray[headArray.length] = element; et la branche de récursivité est stoppée. Si l'élément n'est pas de type titre Hn, la fonction est rappelée récursivement, en passant le nœud dans lequel on se situe headTag(element, headArray); jusqu'à ce que tous les nœuds enfants aient été parcourus.

IV-B. Extraction du texte contenu dans un élément de type titre

Admettons qu'un des titres contiennent d'autres éléments HTML comme ceci : <h1>Un titre en <b>gras</b> et <br /> saut de ligne</h1>. Lors de l'affichage de la table des matières, il faut que la présentation soit homogène. Seul, le texte est nécessaire. On doit pouvoir ressortir uniquement : Un titre en gras et saut de ligne. Les balises internes au titre pourraient être à l'origine d'un affichage disgracieux ou d'un document non valide, une fois la table des matières générée. De ce fait, l'utilisation des fonctions du DOM est encore requise afin d'assurer une plus grande compatibilité avec les navigateurs. Aucune fonction n'existe actuellement pour effectuer ce type de traitement. Cela sent la récursivité tout ça !

 
Sélectionnez
  function inText(node) {

    var childrenNumber = node.childNodes.length;

    var foundString = "";

    if (childrenNumber == 0) {
      return node.nodeValue;
    }
    else { 

      for (var i = 0; i < childrenNumber; i++) {

        foundString += inText(node.childNodes[i]);

      }

      return foundString;

    }

  }

Cette fonction prend comme argument un nœud et retourne la concaténation des nœuds textuels. Les nœuds HTML sont simplement éliminés. On compte d'abord le nombre de nœuds enfants du nœud passé en paramètre avec l'instruction var childrenNumber = node.childNodes.length;. On initialise également la chaîne retournée var foundString = "";. Si le nombre de nœuds enfants est égal à zéro, il s'agit d'un nœud texte, la récursivité est terminée, on retourne alors le contenu du nœud. Si le nombre de nœuds enfants est différent de zéro, pour chaque nœud enfant, on concatène ce qui a déjà été trouvé avec ce qui sera trouvé dans les nœuds enfants : foundString += inText(node.childNodes[i]);. Le résultat est ensuite retourné.

IV-C. Mise en place de la table des matières

Après quelques réflexions, je me suis dit qu'il pourrait être intéressant de ne récupérer que les titres contenus dans une seule partie d'un document. Une page peut très bien contenir des titres qui ne font pas partie du contenu lui-même. Pour s'en convaincre, il n'y a qu'à observer cette page. Le contenu est structuré, les menus également. Il est intéressant de pouvoir proposer une table des matières sur le contenu uniquement et pas sur la totalité de la page.

Signature de la fonction :

 
Sélectionnez
contentTable(contentId, insertBeforeId, containerId, minHead, maxHead, tableHeadLevel, clickable)
  1. Le contenu doit être délimité par une balise div par exemple. Cette balise doit posséder un identifiant unique id. Il va permettre à la fonction d'aller chercher tous les éléments titres qui se trouvent dans la balise qu'il identifie. Cet identifiant doit être passé dans la variable contentId.
  2. Une fois générée, la table des matières doit être insérée dans l'arborescence. Elle sera placée juste avant l'élément ayant comme identifiant celui contenu dans la variable insertBeforeId. Ce peut être l'élément délimitant le contenu ou tout autre élément portant un identifiant.
  3. containerId spécifie quel identifiant aura la table des matières. Ceci est utile pour une feuille de style par exemple. Attention, selon les DTD, un identifiant doit être unique dans une même page, donc veillez à bien indiquer un identifiant jamais utilisé au sein de la page.
  4. minHead permet d'indiquer jusqu'à quel niveau inférieur les titres sont pris en compte. Dans un document, il n'est pas forcément nécessaire de référencer la totalité des titres. On peut ne pas avoir besoin d'insérer les titres de niveau 5 et 6 dans la table des matières. Dans ce cas précis, il suffit de passer la valeur 4 : les titres H4 et hiérarchiquement supérieurs seront alors pris en compte. En cas de passage de valeur erronée, sa valeur par défaut est 6.
  5. maxHead permet d'indiquer jusqu'à quel niveau supérieur les titres sont pris en compte. Le fait de lui affecter la valeur 2 permettra d'ignorer les titres de niveau 1. Ce paramètre doit être combiné avec minHead. En cas de passage de valeur erronée ou en désaccord avec le paramètre précédent, sa valeur par défaut est 1.
  6. Afin de donner du sens à la table des matières, il lui faut un titre. Le paramètre tableHeadLevel permet d'indiquer de quel niveau sera ce titre. Si une valeur erronée est passée, sa valeur par défaut est 2.
  7. clickable permet de rendre la table des matières un peu plus dynamique. Un clic sur son titre l'affiche ou la cache. Elle doit prendre comme valeur un booléen. true active le dynamisme et false le désactive.

IV-C-1. Paramétrage interne

Afin de ne pas engendrer une signature de fonction trop longue, certains paramètres doivent être modifiés directement dans le code, dans l'en-tête de la fonction contentTable.

 
Sélectionnez
  function contentTable(contentId, insertBeforeId, containerId, minHead,
                        maxHead, tableHeadLevel, clickable) {

    var contentTableTitle = 'Table des matières';
    var anchorName = 'tableDesMatieres';
    var anchorsNumberingBeginning = 0;
    var openedClass = 'ouvert';
    var closedClass = 'ferme';
  • contentTableTitle contient le titre placé juste avant la table des matières.
  • anchorName contient le préfixe utilisé pour chaque ancre générée. Cette variable n'a aucune incidence visuelle dans la page. Le nom de l'ancre reste cependant visible dans la barre d'adresse et dans la barre d'état.
  • Toutes les ancres générées le sont grâce au suffixe anchorName auquel est ajouté un nombre incrémenté à chaque nouvelle ancre. anchorsNumberingBeginning indique la valeur initiale de ce compteur. Elle doit être numérique et entière.
  • Si le paramètre clickable a pour valeur true, il est possible d'affecter une classe au titre de la table, en fonction qu'elle soit ou non affichée. openedClass indique le nom de classe qu'aura le titre lorsque la table est dépliée. Cette classe est utile avec les feuilles de style.
  • closedClass indique le nom de classe qu'aura le titre lorsque la table est pliée. Cette classe est utile avec les feuilles de style.

IV-C-2. Quelques formalités avant de commencer

Avant tout traitement, il convient de vérifier si le DOM est reconnu par le navigateur. Si la fonction getElementById n'est pas reconnue, on quitte la fonction. De ce fait, aucune table des matières ne sera affichée. Le document reste cependant lisible et valide.

 
Sélectionnez
if (!document.getElementById) return;

Il convient également de tester les valeurs passées à la fonction.

 
Sélectionnez
    if (!minHead || minHead < 1 || minHead > 6) {
      minHead = 6;
    }

    if (!maxHead || maxHead < 1 || maxHead > minHead) {
      maxHead = 1;
    }

    if (!tableHeadLevel || tableHeadLevel < 1 || tableHeadLevel > 6) {
      tableHeadLevel = 2;
    }


    if (document.getElementById(containerId)) {

      alert(containerId + ' already exists in this page!');
      return;

    }
    else if (!document.getElementById(insertBeforeId)) {

      alert(insertBeforeId + ' is not an existing id!');
      return;

    }
    else if (!document.getElementById(contentId)) {

      alert(contentId + ' is not an existing id!');
      return;

    }
    else {
        // tout est ok, on attaque la construction de la table
  • minHead doit être compris entre 1 et 6. Si ce n'est pas le cas, la valeur par défaut 6 lui est affectée.
  • maxHead doit être compris entre 1 et minHead. Si ce n'est pas le cas, la valeur par défaut 1 lui est affectée.
  • tableHeadLevel doit être compris entre 1 et 6. Si ce n'est pas le cas, la valeur par défaut 2 lui est affectée.
  • containerId doit être un identifiant non déjà présent dans la page puisque ce sera l'identifiant de la table des matières et qu'un identifiant doit être unique dans une même page. Si l'identifiant existe déjà, un message d'erreur est affiché, puis le script est stoppé.
  • insertBeforeId et contentId, eux, doivent exister. Si ce n'est pas le cas, un message d'erreur est affiché et le script est stoppé.

IV-C-3. Récupération des nœuds titres

L'affichage de la table des matières pourra donc s'effectuer, car tous les identifiants passés en paramètre sont valides. De cette façon, le document reste bien formé à condition qu'il l'ait été auparavant. Il n'y a plus qu'à parcourir l'arbre HTML. Le script crée le nœud conteneur pour la table des matières. Ensuite, ce nœud est nommé avec l'identifiant passé en paramètre, de cette façon vous pourrez utiliser une feuille de style pour définir l'apparence de la table des matières. Le nœud contenant les titres à référencer est également mis dans la variable content. Le tableau qui contiendra tous les nœuds titres est déclaré. Ensuite ce tableau est passé en paramètre à la fonction headTag ainsi que le nom du nœud à parcourir. Une fois que la fonction se termine, le tableau chapters doit normalement contenir les nœuds titres. Si le nombre de titres trouvés est inférieur à 2, il n'est pas nécessaire de créer une table des matières, donc on quitte.

 
Sélectionnez
      var TCContainer = document.createElement('div');

      TCContainer.id = containerId;

      var content = document.getElementById(contentId);

      var chapters = Array();

      headTag(content, chapters);

      if (chapters.length < 2) return;

IV-C-4. Parcours du tableau de nœuds titres, génération de la table des matières

Le titre de la table des matières est créé : niveau du titre ainsi que son contenu textuel. Ensuite ce titre est inséré dans le nœud table des matières, il s'agit alors du premier nœud inséré. L'élément de liste non ordonnée ul est créé. Si le paramètre clickable est vrai, on ajoute un événement onclick au titre de la table des matières. Cet événement fait appel à la fonction TCSwap (voir plus bas) en lui passant l'identifiant de la table des matières ainsi que les noms de classes ouvertes et fermées. On l'initialise à l'état ouvert en lui donnant le nom de classe correspondant (afin que vous puissiez lui affecter un style CSS). Un événement onclick appelant la fonction TCSwap est également ajouté à la liste, de cette façon, lorsque l'utilisateur clique sur un élément de la liste, il est renvoyé au titre correspondant dans la page, et la table des matières se replie. Un clic sur le titre de la table des matières suffit à l'afficher de nouveau.

 
Sélectionnez
      var TCTitle = document.createElement('h' + tableHeadLevel);
      TCTitle.appendChild(document.createTextNode(contentTableTitle));

      TCContainer.appendChild(TCTitle);


      var theList = document.createElement('ul');


      if (clickable) {

        TCTitle.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

        TCTitle.className = openedClass;

        theList.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

      }

On parcourt la totalité des titres référencés dans le tableau chapters. Si le titre n'est pas du niveau attendu (non compris entre h2 et h5 par exemple), le nœud est ignoré et on passe au suivant. Si le titre est bien du niveau attendu, on crée un élément de liste li. Ensuite on crée un lien (élément a et on lui affecte le contenu textuel uniquement du titre correspondant à l'aide de la fonction inText. Afin de pouvoir exploiter la liste avec une feuille de style, notamment pour l'indentation, on donne à l'élément de liste le type de titre pointé : ainsi un élément de liste se verra attribuer un nom de classe tel que h1, h2 h3, h4, h5 ou h6.

 
Sélectionnez
      for (var i = 0; i < chapters.length; i++) {

        var titleNumber = parseInt(chapters[i].nodeName.charAt(1));


        if (titleNumber <= minHead && titleNumber >= maxHead) {

          var anItem = document.createElement('li');

          var aLink = document.createElement('a');
          aLink.appendChild(document.createTextNode(inText(chapters[i])));

          anItem.className = chapters[i].nodeName.toLowerCase();

Si le titre possède déjà un identifiant, on va l'utiliser pour créer le lien interne dans la page (ancre). Dans le cas contraire, on va tenter de générer un identifiant jusqu'à ce qu'il soit unique. Une fois l'identifiant généré, on l'insère dans le titre correspondant, puis on va l'utiliser pour créer le lien interne. Une fois le lien fait, il est inséré dans l'élément de liste qui, à son tour est inséré à la suite des autres éléments dans la liste.

 
Sélectionnez
          if (chapters[i].id) {

            aLink.href = '#' + chapters[i].id;

          }
          else {

            do {

              anchorsNumberingBeginning++;

            } while (document.getElementById(anchorName + anchorsNumberingBeginning))


            chapters[i].id = anchorName + anchorsNumberingBeginning;

            aLink.href = '#' + chapters[i].id;

          }

          anItem.appendChild(aLink);

          theList.appendChild(anItem);

        }

      }

La liste est ajoutée dans le conteneur de la table des matières juste après le titre de la table des matières. On récupère le nœud devant lequel on va insérer la table des matières. Ensuite on récupère son nœud père. Il n'y a plus qu'à réaliser l'insertion dans l'arbre HTML.

 
Sélectionnez
      TCContainer.appendChild(theList);

      var beforeElement = document.getElementById(insertBeforeId);

      var theParent = beforeElement.parentNode;

      theParent.insertBefore(TCContainer, beforeElement);

    }

  }

V. Fonction de pliage/dépliage de la table des matières

Cette fonction reçoit l'identifiant de la table des matières ainsi que le nom de classe ouverte et le nom de classe fermée. Le nœud liste ul est stocké dans la variable linkList. Le nœud titre hn est stocké dans la variable listTitle. Ce sont respectivement les premiers et derniers enfants du nœud table des matières. Si la liste n'est pas cachée, on la cache grâce à la valeur none. Le titre prend alors comme nom de classe classClosed. Si la liste n'est pas affichée, on l'affiche grâce à la valeur block. Le titre prend alors comme nom de classe classOpened.

 
Sélectionnez
  function TCSwap(containerId, classOpened, classClosed) {

    var linkList = document.getElementById(containerId).lastChild;
    var listTitle = document.getElementById(containerId).firstChild;

    if (linkList.style.display != 'none') {

      linkList.style.display = 'none';

      listTitle.className = classClosed;

    }
    else if (linkList.style.display != 'block') {

      linkList.style.display = 'block';

      listTitle.className = classOpened;

    }

  }

VI. Exemple d'utilisation

Pour commencer, il est nécessaire d'inclure le script JavaScript dans la page HTML. Il suffit de la faire à l'aide de la balise script. Le fichier contenu dans l'archive zip est encodé en iso-8859-15.

 
Sélectionnez
<script type="text/javascript" src="contentTable.js" charset="iso-8859-15"></script>

Ensuite, au chargement de la page - donc dans l'événement onload de la balise body - il suffit d'appeler la fonction contentTable en spécifiant les arguments nécessaires.

  1. Le premier est l'identifiant de la balise dans laquelle le script ira chercher les titres.
  2. Le second est l'identifiant de la balise devant laquelle sera insérée la table des matières. C'est à vous de faire attention à ne pas insérer la table des matières qui est un élément de type block, dans un élément en ligne par exemple.
  3. Le troisième paramètre est l'identifiant qu'aura la table des matières. Cet identifiant est utile notamment pour l'attribution de styles CSS à la table des matières.
  4. Le quatrième indique quel est le niveau minimal des titres qui seront mis dans la table des matières : en mettant 3, les titres de niveaux 4, 5 et 6 ne seront pas référencés dans la table des matières.
  5. Le cinquième indique quel est le niveau maximal des titres qui seront mis dans la table des matières : en mettant 2, les titres de niveau 1 ne seront pas référencés dans la table des matières.
  6. Le sixième paramètre permet d'indiquer de quel niveau sera le titre de la table des matières.
  7. Le septième permet d'indiquer si la table des matières est rétractable ou non. true indique que oui, false indique que non. Un clic de souris sur le titre de la table des matières permet de la plier ou de la déplier.
 
Sélectionnez
<body onload="contentTable('sujet', 'remerciements', 'tdm', 3, 2, 2, false);">

Voyez le résultat du code ci-dessus appliqué à l'exemple 1. Le script génère la table des matières juste avant le titre remerciements. La table des matières aura pour identifiant tdm et son titre sera de niveau 2 (h2). Seuls les titres de niveaux supérieurs ou égaux à 3 et inférieurs ou égaux à 2 de la section sujet seront référencés dans la table des matières. Ici, la table des matières n'est pas pliable. L'exemple 3 prend en compte les titres de niveaux 2 à 4. L'exemple 4 est rétractable, cliquez sur le titre de la table des matières pour vous en rendre compte.

Enfin, le dernier exemple montre l'utilisation de la table des matières avec une feuille de style. Voici la feuille de style utilisée dans l'exemple.

 
Sélectionnez
#tdm * {
  margin:0; 
  padding:0;
}

#tdm {
  border:2px solid #AABBDD;
  margin:0;
  width:280px;
  font-family:Verdana, Arial, Helvetica, sans-serif;
  font-size:small;
  position:absolute;
  top:10px;
  right:10px;
  background-color:#EEEEFF;
}

#tdm h1, #tdm h2, #tdm h3, #tdm h4, #tdm h5, #tdm h6 {
  font-size:medium;
  text-align:center;
  display:block;
  background-color:#CCDDFF;
  cursor:pointer;
}

#tdm .ouvert {
  background-image:url('imgs/moins.gif');
  background-repeat:no-repeat;
  background-position:left center;
}

#tdm .ferme {
  background-image:url('imgs/plus.gif');
  background-repeat:no-repeat;
  background-position:left center;
  background-color:#BBCCDD;
}

#tdm ul {
  margin:5px;
  list-style-type:outside;
}

#tdm li.h1 {
  margin-left:20px;
  list-style-type:square;
  color:#000000;
}

#tdm li.h2 {
  margin-left:30px;
  list-style-type:disc;
  color:#000033;
}

#tdm li.h3 {
  margin-left:40px;
  list-style-type:circle;
  color:#000066;
}

#tdm li.h4 {
  margin-left:50px;
  list-style-type:circle;
  color:#000099;
}

#tdm li.h5 {
  margin-left:60px;
  list-style-type:circle;
  color:#0000CC;
}

#tdm li.h6 {
  margin-left:70px;
  list-style-type:circle;
  color:#0000FF;
}

#tdm li a {
  text-decoration:none;
  color:#000000;
}

#tdm li a:hover, #tdm li a:focus {
  text-decoration:underline;
  color:#666666;
}

Vous pouvez utiliser toute sorte de styles. Les seules choses que vous devez connaître pour créer vos styles sont : l'identifiant que vous avez donné à la table des matières, le nom de classe que possédera le titre lorsqu'il sera ouvert ou fermé (paramétrable au sein de la fonction), le nom de classe que possédera chaque élément de liste (h1 à h6, non paramétrable à moins de toucher au code).

VII. Pour conclure

Vous pouvez modifier, redistribuer le script table des matières tant que la licence et le nom de l'auteur restent mentionnés dans le script. N'hésitez pas à me faire part d'un dysfonctionnement ou d'une amélioration à effectuer. Le script table des matières est sous licence GPL, ce qui n'est pas le cas de cet article.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2005 Matthieu PETIOT. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.