Accordéon complexe en jQuery

Attention:  cet article date du 11 novembre 2012
Ce qu'il contient est peut être encore valable...
... ou complètement obsolète!

Mettre en place un accordéon en jQuery est assez simple si la structure est contrôlée. La plupart du temps, un déclencheur sur lequel on clique, et qui ouvre la div suivante, laquelle contient ce qu’il faut cacher/montrer.

Mais que faire si l’on ne maitrise pas la dite structure, et que l’on veut jouer sur tout ce qui suit le déclencheur, jusqu’au suivant, sans que tout ceci soit enrobé d’une div?

Note:
Ce tuto est périmé, suite à la suppression de l’évènement toggle() de jQuery
Dès que je peux, j’adapte!

Accordéon de base…

Voyons déjà comment gérer la solution idéale, celle que l’on maîtrise totalement.

Structure html

Typiquement, nous mettrons le tout dans une div enveloppante (ici avec une classe accordeon) qui alternera des <h4> (déclencheur) qui ouvriront les <div> suivantes (avec une classe « soufflet »)

<div class="accordeon">
    <h4>CLICK ICI accordeon A</h4>
        <div class="soufflet">...</div>
    <h4>CLICK ICI accordeon B</h4>
        <div class="soufflet">...</div>
    <h4>CLICK ICI accordeon C</h4>
        <div class="soufflet">...</div>
</div>

Et son jQuery

Ensuite, ne reste qu’à glisser quelques lignes de jQuery (et sans plugin):

jQuery(document).ready(function() {
   $('.accordeon div.soufflet').hide();
   $('.accordeon h4').toggle(
      function(){  $(this).next('div.soufflet').slideDown(); }
      ,
      function(){ $(this).next('div.soufflet').slideUp(); }
   );
});

La première ligne sert à cacher les soufflets (qui seront visibles par défaut, pour préserver l’accessibilité des quelques rares qui n’ont pas JavaScript).

Suit un toggle qui alternera l’ouverture et la fermeture.

Voir la démo ici même

Simple, efficace…

Quand les choses se gâtent

Dans l’hypothèse qui nous préoccupe (qui m’a été soumise par Marie R. une ancienne élève), nous avons une structure plus complexe, mais plus réaliste aussi. En l’occurrence un déclencheur suivi d’une série de balises qui ne sont contenues dans rien.

Avec impossibilité de changer cette structure, issue d’un CMS.

Structure html

Illustrons ceci, dans une classe bandoneon (pour éviter les conflits…)

<div class="bandoneon">
    <h4>CLICK ICI bandoneon A</h4>
        <p>Lorem</p>
        <p>Ipsum</p>
    <h4>CLICK ICI bandoneon B</h4>
        <ul><li>a</li><li>b</li><li>c</li></ul>
    <h4>CLICK ICI bandoneon C</h4>
        <table><tr><td>1</td><td>2</td><td>3</td></tr></table>
</div>

Nous n’avons donc rien après les <h4>, que des structures hétéroclites (pouvant être des <p>, des <table>, des <ul> ou toute autre balise, voire séries de balises bien sûr.)

Le jQuery… écrire moins, faire plus ?

Première piste, ajouter une classe à toute les balises sauf les <h4> contenue dans bandoneon, le problème se posant alors pour ouvrir les éléments. Lesquels? Ceux qui suivent le déclencheur jusqu’au prochain déclencheur? Pas si simple, et je n’ai pas réussi à trouver un résultat probant (outre que visuellement c’était assez spécial).

La seule solution (théorique) reste la plus logique: comment rajouter des <div> pour encadrer notre contenu global?

J’ai d’abord cherché autour des fonctions wrap, wrapInner, append et autres. Sans résultat probant non plus.

Ne reste plus qu’à créer le code soi même…

Le principe est assez simple:

  • On récupère le contenu html de notre div bandoneon, afin de disposer d’une chaine de caractères, manipulable
  • On découpe la chaine suivant une balise à définir (<h4> dans notre hypothèse)
  • Et on reconstitue notre contenu, en ajoutant au passage les <div class=soufflet> après chaque </h4>, et des </div> avant chaque (sauf le premier bien sûr).

Ce qui nous donne donc:

$('.bandoneon').html( function ( i, html ) {
   var ouverture = '<h4>';
   var fermeture = '</h4>';
   var final = ''; 
   html = html.split( ouverture ); 
   var nombre = $(html).length; 
   $(html).each(function(j, valeur){
      if ( j > 0) {
         ligne = valeur.split( fermeture );
         final = final + ligne[0] + fermeture+'\n';
         final = final + '<div>' + ligne[1] + '</div>\n';
      }
      if (j < nombre-1) final = final + ouverture;
   });
   $('.bandoneon').html( final );
});

Voir la démo (avec son code source, commenté).

Notons que faire un chercher/remplacer dans la chaine aurait pu être possible, mais difficile de gérer le fait que le premier doit être différent des autres…

Ne reste plus qu’à ajouter le bout de code pour gérer l’ouverture et la fermeture, qui ressemble comme deux gouttes d’eau à son petit frère (en changeant la classe, bien sûr):

$('.bandoneon div.soufflet').hide();
$('.bandoneon h4').toggle(
   function(){  $(this).next('div.soufflet').slideDown(); }
   ,
   function(){ $(this).next('div.soufflet').slideUp(); }
);

Et voilà!

Bon, j’imagine que si le contenu est volumineux, les performances risquent d’être assez mauvaises, mais on a rien sans rien, non?

2 réponses pour “Accordéon complexe en jQuery”

  1. Jerome a dit:

    Bonjour, je me suis également penché sur le problème de Marie R, et je suis arrivé à une solution qui marche également.

    Est-elle plus efficace ?

    jQuery(document).ready(function(){
    $(« #bandoneon p »).hide();
    $(« #bandoneon table »).hide();
    $(« #bandoneon ul »).hide();
    $(« #bandoneon h4 »).click(function(){
    var ok = false;
    var prochainH = $(this).next();
    do {
    if (prochainH.get(0).tagName == « H4 »)
    {
    ok = true;
    }
    else
    {
    prochainH.toggle();
    prochainH= prochainH.next();
    }
    }
    while(!ok);
    });
    });

    • R. Carlier a dit:

      Il fonctionne également, et l’approche est intéressante.

      Explication pour le lecteurs: on clique sur le h4, il va aller chercher les balises suivantes et si ce ne sont pas des h4 il les cache ou les montre.

      Je dirais par contre qu’il est moins « souple ». Si on se contente de faire un toggle (cacher/montrer sans effet) c’est parfait. Mais si l’on doit appliquer des effets plus élaboré (slideUp, slideDown, voir d’autres choses comme ajouter des classes aux h4 – par exemple pour indiquer quel onglet est ouvert, lequel est fermé… ou simplement fermer les autres volets) il devient plus lourd de l’adapter. D’autant que les slideUp/down s’appliqueront à chaque conteneur, ce qui donne un effet assez bizarre (que j’obtenais en utilisant les wrap de jQuery à toutes balises non h4 contenu dans .bandoneon).

      Autre point « négatif » : les performances. A chaque click, ton script re-parcourt l’arbre; le mien ne le fait qu’une fois, les div étant en place on y touche plus.

      Note aux lecteurs: pour avoir la source de Jerôme proprement mise en page, voir exemple 3

Laisser une réponse

Catégories

Archives