Navigation Bar, div anzeigen beim klicken auf einen Link
bearbeitet von
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.
~~~js
document.querySelectorAll(".detailsGroup details")
.forEach(d => d.addEventListener("toggle", detailsToggler));
~~~
Hier passiert schon eine ganze Menge.
`document.querySelectorAll("...")` - diese [DOM](https://wiki.selfhtml.org/wiki/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 haben.
CSS: [class selector (Klassenselektor) und element selector (Typselektor)](https://wiki.selfhtml.org/wiki/CSS/Selektoren/einfacher_Selektor), und descendant combinator ([Nachfahrenkombinator](https://wiki.selfhtml.org/wiki/CSS/Selektoren/Kombinator/Nachfahrenkombinator))
Das Ergebnis von querySelectorAll ist eine [NodeList](https://wiki.selfhtml.org/wiki/JavaScript/DOM/Node#NodeList). Die neueren Browser kennen ein paar NodeList-Methoden mehr als unser Wiki; im Internet Explorer würde der forEach Aufruf ohne einen [Polyfill](https://wiki.selfhtml.org/wiki/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](https://wiki.selfhtml.org/wiki/JavaScript/Funktion#Lambda-Ausdruck_.28oder_Pfeilfunktion.29) 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.
~~~js,bad
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](https://wiki.selfhtml.org/wiki/JavaScript/DOM/Event#.C3.9Cbersicht)-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](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDetailsElement), 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.
~~~js,good
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:
~~~html,bad
<div id="detailsGroup">...</div>
~~~
Das muss natürlich
~~~html,good
<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](https://www.shirtinator.de/t-shirts/6-stufen-des-debugging-shirtinator-maenner-t-shirt-20121).
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](https://wiki.selfhtml.org/wiki/JavaScript/Schleife#Schleifen_mit_.22for...of.22) 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