Rolf B: Navigation Bar, div anzeigen beim klicken auf einen Link

Beitrag lesen

Hallo ArneS,

wenn Du mit einer Akkordeon-ähnlichen Lösung zufrieden bist, dann ist es ja gut.

Mein Code war ziemlich komprimiert und eher für Gunnars Niveau gedacht. Und leider sind ein paar Bugs drin, deshalb bitte ich Dich, Dir das hier - bzw. das alte Posting - nochmal anzuschauen. Ich hab's im alten Posting korrigiert.

document.querySelectorAll(".detailsGroup details")
        .forEach(d => d.addEventListener("toggle", detailsToggler));

Hier passiert schon eine ganze Menge.

document.querySelectorAll("...") - diese DOM Methode durchsucht den Knoten, auf dem sie aufgerufen wird, nach Elementen, die zum angegebenen CSS Selektor passen. In diesem Fall das ganze Dokument, und sie sucht <details> Elemente, die ein Element mit class="detailsGroup" als Elternelement unter ihren Vorfahren haben.

CSS: class selector (Klassenselektor) und element selector (Typselektor), und descendant combinator (Nachfahrenkombinator)

Das Ergebnis von querySelectorAll ist eine NodeList. Die neueren Browser kennen ein paar NodeList-Methoden mehr als unser Wiki; im Internet Explorer würde der forEach Aufruf ohne einen Polyfill nicht funktionieren.

Aber in Chrome, Edge, Firefox und Safari schon, und forEach ruft die Funktion, die da in den Klammern steht, für jeden Eintrag in der NodeList - also für jedes gefundene details-Element, einmal auf.

d => ... ist eine Pfeilfunktion und eine Kurzschreibweise für function(d) { return ... }. Es gibt ein paar wichtige Feinheiten, dazu kannst Du dem Link folgen. Was hier passiert ist: Für jedes details-Element wird addEventListener aufgerufen, um für das toggle-Event die Funktion detailsToggler als Eventhandler zu registrieren.

function detailsToggler(ev) {
   if (!ev.target.open) return;
   for (let d of document.querySelectorAll("details")) {
      if (d != ev.target)
         d.open = false;
   }
}

Was diese Funktion tut, ist einfacher. Und leider nicht ganz richtig.

Der Eventhandler für toggle wird aufgerufen, wenn der "geöffnet" Zustand des details Elements umschaltet. Von open nach close, oder von close nach open. Wir möchten dann etwas tun, wenn wir vom Öffnen eines Elements erfahren - andernfalls würden wir, wie von Gunnar beschrieben, in einer Endlosschleife landen.

Ein Eventhandler, der mit addEventListener registriert wurde, bekommt ein Event-Objekt als Parameter übergeben. Die target Eigenschaft dieses Objekts ist das DOM Element, für das das Event ausgelöst wurde. Also unser details Element.

Auf diesem Element wird die open Eigenschaft geprüft. Das ist eine spezielle Eigenschaft des HTMLDetailsElement-Objekts, mit dem <details> im DOM dargestellt wird. Nur, wenn das Element offen ist, dann wurde es gerade geöffnet, und wir können die anderen Elemente schließen. Für dieses Schließen wird es ebenfalls ein toggle Event geben, aber darauf reagieren wir wegen dieser Prüfung dann nicht.

Die Funktion sucht sich nun alle <details> Elemente zusammen - und das ist ein Bug, bzw. ein Relikt einer älteren Fassung. Das Script sucht ja nur alle <details> Elemente innerhalb eines class="detailsGroup" Containers. Davon könnte es auch mehrere geben, aber das Schließen anderer <details> darf sinnvollerweise nur innerhalb des gleichen Containers erfolgen.

function detailsToggler(ev) {
   if (!ev.target.open) return;
   let container = ev.target.closest(".detailsGroup");
   for (let d of container.querySelectorAll("details")) {
      if (d != ev.target)
         d.open = false;
   }
}

Und dabei fällt dann gleich ein zweiter Bug auf:

<div id="detailsGroup">...</div>

Das muss natürlich

<div class="detailsGroup">...</div>

sein, sonst könnte man ja nicht mehrere davon verwenden. Und die programmierten Selektoren setzen eh schon Klassen voraus. Ein Wunder, dass es vorher irgendwie geklappt hat. Ich habe mir ein T-Shirt verdient.

Nach der Korrektur *räusper* ist es jedenfalls so, dass mittels der closest Methode das nächstliegende Elternelement gesucht wird, auf das der angegebene CSS Selektor zutrifft. Das ist das <div class="detailsGroup">. Und darauf wird wiederum mit querySelectorAll die NodeList der enthaltenen <details> Elemente ermittelt.

Diese wird nun mit der for...of-Schleife durchlaufen. Das ist ähnlich zu .forEach, und ich hätte for...of auch beim Registrieren der Events verwenden können. Ich habe an dieser Stelle kein .forEach verwendet, weil da mehr als nur ein einziger Funktionsaufruf passiert.

Ich muss nämlich prüfen, ob das gefundene <details> Element mit dem Event-Target übereinstimmt. Nur, wenn das nicht der Fall ist, darf ich es schließen. Andernfalls machte ich mein frisch geöffnetes <details> gleich wieder zu.

Alles klar?

Rolf

--
sumpsi - posui - obstruxi