Zur Navigation

Zwei Navis (desktop-first), die mobile per Toggle-Klickbutton aus-/einklappen sollen [3]

21 Martin

Mit den leeren arrays, also folgenden const

const dropdownButton = [];
const dropdownMenue = [];
const headnavButton = document.querySelector('.headnav-button');
const headnavMenue = document.querySelector('.headnav-haupt-ul');
const menueLinks = document.querySelectorAll('.headnav-haupt-ul li a');

...funktioniert das Ausklappen wieder, aber das Zuklappen funktioniert in keiner Weise, auch nicht beim Klick auf den Button (toggle) oder in den body (wie testet man eigentlich ein Zuklappen nach Menülink-Klick, wenn dann der Link auf eine andere Seite führt?).
Es kommt immer noch folgender Fehler in der Konsole (scheint also nicht zu klappen mit den "Ersatz-arrays"):


Uncaught TypeError: headnavMenue.forEach is not a function
closeDropdownMenu
<anonymous>
EventListener.handleEvent*

Dasselbe bei den const mit nicht vorhandenen Klassen (andere Variante).

22.09.2025 23:50

22 Jörg Kruse

Uncaught TypeError: headnavMenue.forEach is not a function

Du hast vermutlich immer noch das im Code stehen:

function closeDropdownMenu() {
    headnavMenue.forEach((drop) => {

Im Original findt sich dort aber die Array-Konstante dropdown:

function closeDropdownMenu() {
  dropdown.forEach((drop) => {

Ich glaube aber, es gibt da noch ein grundsätzliches Missverständnis, was das Script tut. Dessen Arbeitsweise lässt sich auf Codepen nachvollziehen:

https://codepen.io/evavic44/pen/QWZYEPQ

Du kannst durch Ziehen des Fensters zwischen Mobile- und Desktop-View wechseln. Mit der Taste "Esc" lässt sich ein geöffnetes Untermenü ("dropdown") schließen, nicht aber das Gesamtmenü ("navMenu"). Gleiches gilt für den Klick in den Body.

Du möchtest anscheinend, dass bei einem "Esc" auch das Gesamtmenü schließt. Diese Funktionalität ist in dem Script aber nicht enthalten. Möglicherweise, weil diese auf die Mobile-View begrenzt werden muss, was die Umsetzung etwas aufwendiger macht.

(wie testet man eigentlich ein Zuklappen nach Menülink-Klick, wenn dann der Link auf eine andere Seite führt?)

Durch einen Link auf einen Anker auf der selben Seite, wie z. B. "#section-1"

Wenn eine andere Seite geladen wird, ist dieser Effekt aber eh nur sehr kurz sichtbar (wenn überhaupt)?

23.09.2025 18:29

23 Martin

Du hast vermutlich immer noch das im Code stehen:

function closeDropdownMenu() {
headnavMenue.forEach((drop) => {


Im Original findt sich dort aber die Array-Konstante dropdown:

function closeDropdownMenu() {
dropdown.forEach((drop) => {

Stimmt, ich habe beide Namen, auch dropdownButton, unten bei den beiden function-Codes korrigiert, also die Array-Varianten stehen dort jetzt.
Den letzten ausgeklappten Link, ganz unten (bei mir zu Hause der Kontakt-Link) habe ich zum Ankerlink #top gemacht. Das ist auf meiner Seite der ohnehin schon existierende nach-oben-Link. Gute Idee von dir.

Du möchtest anscheinend, dass bei einem "Esc" auch das Gesamtmenü schließt. Diese Funktionalität ist in dem Script aber nicht enthalten. Möglicherweise, weil diese auf die Mobile-View begrenzt werden muss, was die Umsetzung etwas aufwendiger macht.

Ich vermute, dass ESC zum Schließen eines Gesamtmenüs genau so viel Sinn macht, wie zum Schließen eines Untermenüs, oder?
Dann ja.
Dass das freecode-Beispiel damit ein Mal mehr auf mein "einfaches Gesamtmenü" angepasst werden muss, weil es eine komplexere, zusätzliche Untermenü-Struktur hat, kann natürlich sein. Muss dadurch der Code des 3. Close-Grundes ganz anders gestaltet werden? Er greift wie die beiden anderen Close-Gründe auf die zwei Funktionen closeDropdownMenu und setAriaExpandedFalse zu. Das wäre gut, wenn es so bliebe, aber wenn es dadurch recht kompliziert wird...

Oder hat das vielleicht irgendwie damit zu tun, dass freecode eine gemeinsame Klasse "menu" für sowohl nav als auch div (in dem ul drinsteckt) zu tun? Vielleicht kann man mit so einer gemeinsamen Klasse für zweiversch. Elemente ggf. etwas deichseln. Nur eine Idee...

Mit dem folgenden, gesamten JS-Code, kommt jetzt zwar keine Fehlermeldung mehr, aber es klappen leider neben ESC auch die beiden anderen Close-Gründe noch immer nicht. Nur das Toggle-Schließen auf den Button klappt.

// button, ul der headnav, ul-Links der headnav als Konstanten definiert, selektiert mit ihren Klassen
// menueLinks brauche ich für close-Grund 1
const dropdownButton = [];
const dropdownMenue = [];
const headnavButton = document.querySelector('.headnav-button');
const headnavMenue = document.querySelector('.headnav-haupt-ul');
const menueLinks = document.querySelectorAll('.headnav-haupt-ul li a');


// Button bekommt eine EventListener-Klick-Funktion zugewiesen und ihr Name festgelegt
headnavButton.addEventListener("click", toggleButton);


// Jetzt wird die Funktion "toggleButton" definiert, angelehnt an freecode-Bsp.
// Ist die Bedingung für false erfüllt, wird true zurückgegeben, sonst false
function toggleButton() {
    headnavMenue.classList.toggle("menue-zeigen"); //im MQ neu angelegte Klasse, die das Menü zeigt
    headnavButton.setAttribute(
        "aria-expanded",
        headnavButton.getAttribute("aria-expanded") === "false" ? "true" : "false"
    );
}


// Zweite Funktion, angelehnt an freecode-Bsp., dropdownBtn durch headnavButton ersetzt
function setAriaExpandedFalse() {
    dropdownButton.forEach((btn) => btn.setAttribute("aria-expanded", "false"));
    }


// Dritte Funktion, angelehnt an freecode-Bsp., dropdown durch headnavMenue ersetzt
function closeDropdownMenu() {
    dropdownMenue.forEach((drop) => {
    drop.classList.remove("active");
    drop.addEventListener("click", (e) => e.stopPropagation());
    });
}


// close-Grund 1: close dropdown menu when the dropdown links are clicked
menueLinks.forEach((link) =>
    link.addEventListener("click", () => {
        closeDropdownMenu();
        setAriaExpandedFalse();
    })
);


// close-Grund 2: close dropdown menu when you click on the document body
document.documentElement.addEventListener("click", () => {
    closeDropdownMenu();
    setAriaExpandedFalse();
});


// close-Grund 3: close dropdown when the escape key is pressed
document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") {
        closeDropdownMenu();
        setAriaExpandedFalse();
    }
});

23.09.2025 23:02

24 Jörg Kruse

So könnte der Freecodecamp-Code so erweitert werden, dass auch das Gesamtmenü beim Druck auf "Esc" geschlossen wird:

document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") {
    closeDropdownMenu();
    setAriaExpandedFalse();
    if (window.getComputedStyle(hamburgerBtn).display == "block") {
      navMenu.classList.remove("show");
    }
  }
});

Wenn hamburgerBtn sichtbar ist (also in der mobile View), wird dann zusätzlich navMenu nicht mehr angezeigt. Auf der Codepen-Seite funktioniert diese Anpassung.

Für hamburgerBtn, navMenu und "show" musst du in deinem Code entsprechend deine Bezeichner einsetzen...

24.09.2025 11:25 | geändert: 24.09.2025 11:35

25 Jörg Kruse

Nachtrag:

Bei einem Klick auf den Link eines Untermenüs wird im Freecodecamp-Beispiel auch das Gesamtmenü geschlossen durch den Funktionsaufruf "toggleHamburger();".

Mit einem Klick auf den Body das Menü zu schließen, ergäbe in dem Freecodecamp-Beispiel keinen Sinn, da das Menü im Mobile View den gesamten Viewport einnimmt.

24.09.2025 14:07

26 Martin

Mit dem neuen Code klappt nun das Schließen per ESC. Danke!

Bei einem Klick auf den Link eines Untermenüs wird im Freecodecamp-Beispiel auch das Gesamtmenü geschlossen durch den Funktionsaufruf "toggleHamburger();".

Mit einem Klick auf den Body das Menü zu schließen, ergäbe in dem Freecodecamp-Beispiel keinen Sinn, da das Menü im Mobile View den gesamten Viewport einnimmt.

Es gibt in freecode aber für beides, auch für den body-Klick, extra einen Code. In meinem Code in post 23 habe ich diese zwei Close-Gründe (deren Code) von dort nur abgeschrieben und beim Code für den Link-Klick "links" durch meine const "menueLinks" ersetzt.

Beim Code für den Link-Klick werden offenbar über eine Funktion "link" (nicht links, sondern link), dann wieder die beiden Funktionen closeDropdownMenu und setAriaExpandedFalse aufgerufen.

Klappt aber leider beide Close-Gründe nicht. Ohne Fehlermeldung in der FF-Konsole.

Untermenüs habe ich erst in der nächsten Website. Es ist zwar gut, ein Basis-Bsp. mit Untermenüs zu haben, weil sich darauf später leichter aufbauen lässt.
Aber wie zu Beginn in post 9, 10 gesagt, ist zunächst mal
- ein funktionierendes Menü (gibt ja zwei auf den Seiten)
- ohne Untermenü
gefragt.

24.09.2025 21:02 | geändert: 24.09.2025 21:11

27 Jörg Kruse

Es gibt in freecode aber für beides, auch für den body-Klick, extra einen Code. In meinem Code in post 23 habe ich diese zwei Close-Gründe (deren Code) von dort nur abgeschrieben und beim Code für den Link-Klick "links" durch meine const "menueLinks" ersetzt.

Beim Code für den Link-Klick werden offenbar über eine Funktion "link" (nicht links, sondern link), dann wieder die beiden Funktionen closeDropdownMenu und setAriaExpandedFalse aufgerufen.

Im Original gibt es noch einen dritten Funktionsaufruf "toggleHamburger();", welcher das offene Menü schließt. Die anderen beiden Funktionsaufrufe schließen nur die bei dir nicht vorhandenen Untermenüs.

"link" ist hier übrigens nicht der Name der Funktion, sondern repräsentiert ein einzelnes Link-Element, welches von menueLinks.forEach() als Parameter an die anonyme Arrow-Funktion übergeben wird. Innerhalb der Arrow-Funktion wird ein Event-Listener für "link" erstellt.

25.09.2025 17:24

28 Martin

Im Original gibt es noch einen dritten Funktionsaufruf "toggleHamburger();"

Das ist bei mir die Funktion toggleButton und toggled den einen Button des Hauptmenüs.
Das existiert und funktioniert.

Die anderen beiden Funktionsaufrufe schließen nur die bei dir nicht vorhandenen Untermenüs.


Du meinst, closeDropdownMenu(); und setAriaExpandedFalse(); tun in meinem Code (s. post 23) faktisch bislang noch gar nichts, sondern dienen bislang nur der späteren Ergänzung von Untermenüs?

Das hieße dann wohl, man müsste die beiden noch offenen closing-Gründe so abändern, dass sie auch auf das Hauptmenü anwendbar sind, so ähnlich wie bei deinem ESC-Close-Code (wo die beiden Untermenü-Funktionen ebenfalls vorkommen)?

Vermutlich wäre es günstig, wenn diese beiden, offenbar bislang noch gar nicht aktiven Untermenü-Funktionen als solche bestehen bleiben könnten. Und alle drei Closing-Gründe einheitlich auf diese zwei zugreifen könnten.

Falls ja, dann müsste der Code dieser zwei noch offenen Close-Gründe "so ähnlich" wie der ESC-Code gestaltet werden, nur halt einen Link-Klick und einen body-Klick betreffend?

Falls ja, würdest du das noch machen? Ich sehe dafür keine Vorlage, mit der ich selbst sinnvoll rumprobieren könnte mit meinen 5%-JS-Kenntnissen und Beispielen.

Wenn das erledigt ist, will ich dich nicht länger nerven.

25.09.2025 23:39

29 Jörg Kruse

Im Original gibt es noch einen dritten Funktionsaufruf "toggleHamburger();"

Das ist bei mir die Funktion toggleButton und toggled den einen Button des Hauptmenüs.
Das existiert und funktioniert.

Der Funktionsaufruf fehlt aber noch in dem EventListener für die Links:

menueLinks.forEach((link) =>
    link.addEventListener("click", () => {
        closeDropdownMenu();
        setAriaExpandedFalse();
    })
);

Vergleiche mit dem Original:

links.forEach((link) =>
  link.addEventListener("click", () => {
    closeDropdownMenu();
    setAriaExpandedFalse();
    toggleHamburger();
  })
);

Da menueLinks bei dir alle Links des Menüs enthält, sollte ein Klick auf einen Link dann auch das Menü schließen.

26.09.2025 15:35

30 Jörg Kruse

Falls ja, dann müsste der Code dieser zwei noch offenen Close-Gründe "so ähnlich" wie der ESC-Code gestaltet werden, nur halt einen Link-Klick und einen body-Klick betreffend?

Als erstes würde ich den Code zum Schließen des Menüs in der mobile View in eine Funktion auslagern, die an verschiedenen Stellen aufgerufen werden kann:

function closeMenuIfMobile() {
  if (window.getComputedStyle(hamburgerBtn).display == "block") {
    navMenu.classList.remove("show");
  }
}

Beim 'Esc' und "Klick auf Link" kann man die Funktion dann ganz einfach aufrufen:

links.forEach((link) =>
  link.addEventListener("click", () => {
    closeDropdownMenu();
    setAriaExpandedFalse();
    closeMenuIfMobile();
  })
);

document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") {
    closeDropdownMenu();
    setAriaExpandedFalse();
    closeMenuIfMobile();
  }
});

Beim Klick in den Body muss dabei ein Klick auf das Menü und den Hamburger Button ausgeschlossen werden:

document.documentElement.addEventListener("click", (e) => {
  closeDropdownMenu();
  setAriaExpandedFalse();
  if (! navMenu.contains(e.target) && ! hamburgerBtn.contains(e.target)) {
    closeMenuIfMobile();
  }
});

26.09.2025 16:20