Dans ce tutoriel je vous propose de découvrir comment créer un sommaire dynamique en se basant sur les différents niveaux d'entête d'un texte. Nous allons écrire notre JavaScript sans utiliser de librairies externes afin de ne pas avoir de dépendance. Pour accéder aux différents éléments HTML et interagir avec ces derniers, nous allons utiliser les méthodes querySelector et querySelectorAll. Ces méthodes ne sont pas supportées sur de vieux navigateurs tels qu’Internet Explorer 8, ceci n'est pas gênant, car le JavaScript ne sert ici qu'à améliorer l'interface (sans le sommaire le site reste utilisable).

Le principal problème que l'on va rencontrer avec la création de ce sommaire est la mise en place de la structure HTML pour le présenter. En effet, il va falloir faire correspondre le niveau des entêtes avec la profondeur de l'élément dans la liste. Afin de résoudre le problème le plus simplement possible, nous allons utiliser un tableau pour stocker les <ul> qui accueilleront nos éléments. L'index du tableau représentant le niveau de l'entête (un entête h2 sera placé dans un <ul> à l'index 0, un h3 sera placé dans le <ul> à l'index 1...).

Pour organiser notre code proprement nous allons utiliser une classe instanciable en JavaScript.

// Exemple d'utilisation
var s = new Sommaire(document.querySelector('.container'));
s.appendTo(document.querySelector('#sommaire'));

Pour rendre cette classe instanciable, on va créer une fonction de même nom :

 function Sommaire(container){
  this.container = container; // Container dans lequel se trouve notre texte
  this.uls = [document.createElement('ul')]; // On stock les <ul> dans lequel on va placer nos <li>
  this.buildStructure();
};

Afin de séparer le code, on va gérer la construction de la structure dans une méthode séparée :

// Permet de construire la structure de notre sommaire
Sommaire.prototype.buildStructure = function(){
  // On récupère tous les titres du contenu
  var titles = this.container.querySelectorAll('h2, h3, h4, h5');
  var lastLvl = 0
  for(var i = 0; i < titles.length; i++){
    var title = titles[i];
    var lvl = parseInt(title.tagName.replace('H', '')) - 1; // Niveau du titre, 1:h2 2:h3....
    // Ooops on a sauté plus d'un niveau
    if(lvl - lastLvl > 1){
      throw "Erreur dans la structure des titres, Saut d'un h" + (lastLvl + 1) + " vers h" + (lvl + 1);
    }
    var lastLvl = lvl;
    // On crée le <li> qui va contenir notre titre
    var li = document.createElement('li');
    var a = document.createElement('a');
    a.setAttribute('href', '#');
    a.textContent = title.textContent;
    li.appendChild(a);
    // On a un <ul> parent ?
    if(!this.uls[lvl - 1]){
      var ul = document.createElement('ul');
      this.uls[lvl - 1] = ul;
      this.uls[lvl - 2].lastChild.appendChild(ul);
    }
    this.uls[lvl] = null; // Ce niveau n'a pas de <ul> enfant
    this.uls[lvl - 1].appendChild(li); // On place notre <li> dans le <ul> parent
    this.bindScroll(a, title); // On ajoute l'event sur le lien
  }
};

On sépare ici la partie qui permet de greffer l'événement sur les liens afin d'éviter le problème de portée des variables.

// Au clic sur le a on scroll vers le titre
Sommaire.prototype.bindScroll = function(a, title) {
  a.addEventListener('click', function(e){
    e.preventDefault();
    document.body.scrollTop = title.offsetTop;
  });
}

Enfin, on crée une dernière méthode qui va permettre d'injecter notre sommaire dans n'importe quel élément HTML :

// Ajoute le sommaire à l'élément passé en paramètre
Sommaire.prototype.appendTo = function(element){
  element.appendChild(this.uls[0]);
};

Ceci est un exemple de classe qui permet de résoudre la problématique initiale, on peut évidemment la personnaliser pour y rajouter différentes options (on peut par exemple ajouter la possibilité de changer le style de liste pour une liste ordonnée). Libre à vous d'adapter le code pour correspondre à vos besoins, le but ici est de comprendre le fonctionnement de base dans la création d'un sommaire automatique.