11521 sujets

JavaScript, DOM et API Web HTML5

Bonjour. Je convertis petit à petit mes outils fronts jQuey en vanilla js, et en ce moment c'est au tour de mon menu accordéon (cf. l'ancien menu accordéon en live, et son code jQuery)...

Tant que j'y suis je tente de me mettre au goût du jour en partant non plus d'une liste à remapper mais à partir d'éléments details/summary... que je dois remapper de toute façon car ces éléments se montrent récalcitrants aux animations (pour un tas de raisons que je passerais ici, j'ai fais mes tests ; voir aussi cet exemple issu de Stack Overflow, non merci...).

Donc on a ça au départ :
<div class="accordion">
  <details>
    <summary>Item 1</summary>
    <div>
      <p>Lorem ipsum dolor...</p>
    </div>
  </details>
  <details>
    <summary>Item 2</summary>
    <div>
      <p>Lorem ipsum dolor...</p>
    </div>
  </details>
</div>

Et ça doit donner ça au final :
<div class="accordion">
  <div class="accordion-details">
    <button type="button" class="accordion-summary">Item 1</button>
    <div class="accordion-content" style="">
      <p>Lorem ipsum dolor...</p>
    </div>
  </div>
  <div class="accordion-details">
    <button type="button" class="accordion-summary">Item 2</button>
    <div class="accordion-content" style="">
      <p>Lorem ipsum dolor...</p>
    </div>
  </div>
</div>

Alors j'ai fais une tentative et pas de soucis ça marche (un CodePen en l'état) :
const accordion = (() => {
  const detailss = document.querySelectorAll('.accordion details');
  for (const details of detailss) {
    let summary = details.firstElementChild,
        content = details.lastElementChild;
    summary.outerHTML = '<button type="button" class="accordion-summary">' + summary.innerHTML + '</button>';
    content.outerHTML = '<div class="accordion-content">' + content.innerHTML + '</div>';
    details.outerHTML = '<div class="accordion-details">' + details.innerHTML + '</div>'; // L'élément parent doit être traité en dernier.
  }
  const accordionSummarys = document.querySelectorAll('.accordion-summary');
  for (const accordionSummary of accordionSummarys) accordionSummary.addEventListener('click', () => {
    accordionSummary.parentElement.classList.toggle('open');
    const accordionContent = accordionSummary.nextElementSibling;
    const height = accordionContent.style.maxHeight === accordionContent.scrollHeight + 'px' ? null : accordionContent.scrollHeight + 'px';
    accordionContent.style.maxHeight = height;
  });
})();

Mais cette partie du code (et les lignes qui lui ressemblent) est difficilement modulable :
content.outerHTML = '<div class="accordion-content">' + content.innerHTML + '</div>';

Car par la suite je voudrais ajouter plusieurs fonctionnalités (présentes dans mon ancien code) :
- pendre en compte l'état d'un onglet ouvert,
- fermer tous les éléments frères déjà ouvert lorsqu'un nouvel onglet s'ouvre
- donner un style particulier à un onglet,
- ...
Ce qui me demande de détecter/ajouter encore une ou plusieurs classes à tel ou tel de mes éléments. Je suis sûr qu'il y a une meilleure manière de procéder que ma méthode actuelle, mais comment ?
Modifié par Olivier C (17 Jun 2020 - 16:23)
_laurent a écrit :
Si j'ai bien compris.... avec createElement() et appendChild() ?

Cette solution ne marchait pas chez moi au départ, mais c'est parce que j'avais commit l'erreur de deux boucles for imbriquées. Il faut effectivement que je retente cette solution avant toute chose...
Bon, je ne finirais pas mon implémentation ce soir, c'est assez verbeux... mais en gros je peux remplacer ça :
if (details.open) details.outerHTML = '<div class="accordion-details open">' + details.innerHTML + '</div>';
else details.outerHTML = '<div class="accordion-details">' + details.innerHTML + '</div>';

... par ça :
const html = details.innerHTML,
wrapper = document.createElement('div');
wrapper.classList.add('accordion-details');
if (details.open) wrapper.classList.add('open');
details.parentNode.insertBefore(wrapper, details);
wrapper.appendChild(details).insertAdjacentHTML('afterend', html);
details.parentElement.removeChild(details);


Nettement moins facile à lire, mais bon.
Merci.