====== LU05a – DOM Traversal & NodeLists ======
===== Ausgangslage: Das Accordion-Projekt =====
In diesem Block bauen Sie ein interaktives FAQ Accordion.
{{ :de:modul:m291:learningunits:lu05:theorie:faq-toggle.gif?nolink |FAQ Accordion}}
Laden Sie hier das Figma-File, die Start-HTML und das Readme herunter:
{{ :de:modul:m291:learningunits:lu04:aufgaben:faq-accordion_m291.zip | Accordion Starter}}
**Projektaufbau:** Erstellen Sie drei Dateien: ''index.html'', ''script.js'' und ''style.css''.
{{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-08_at_16.20.42.png?nolink&400 |}}
\\
Verlinken Sie CSS- und JavaScript-File im HTML:
- **CSS:** im '''' mit ''''
- **JS-Script:** am Ende des '''' mit ''''.
\\
Testen Sie zuerst, ob die Verlinkung klappt – dieser Code gehört in ''script.js'':
console.log('Hello World!'); // erscheint das in der DevTools-Console?
Der Aufbau beginnt bewusst **ohne Styling** – Funktionalität zuerst. Das **HTML-Grundgerüst** sieht so aus:
FAQs
It's a small but mighty mission: you'll build an FAQ accordion...
Yes. No coins, no secret handshake...
\\
Anschliessend sollte es so aussehen im Browser:
{{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-05_at_11.48.01.png?nolink&600|}}
===== Schritt 1: Panels (Antworten) mit CSS ein- und ausblenden =====
Bevor wir JavaScript schreiben, definieren wir die zwei möglichen Zustände im CSS. Die Klasse ''open'' wird später per JS gesetzt oder entfernt – das CSS übernimmt das An- und Ausblenden:
Dieser Code kommt ins ''style.css''.
/* Standardmässig werden die Antworten versteckt */
.panel {
display: none;
}
/* Sichtbar werden sie nur, wenn Klasse 'open' gesetzt */
.panel.open {
display: block;
}
Was macht dieser Code (später)?
Er blendet nicht aktive Antworten aus und blended das aktive Element ein (div mit Klasse ''.panel'' bekommt eine zweite Klasse ''.open'' sobald es geklickt wird.):
{{:de:modul:m291:learningunits:lu05:theorie:faq-class-toggle.gif?nolink |}}
===== Schritt 2: querySelector vs. querySelectorAll =====
{{:de:modul:m291:learningunits:lu05:theorie:queryselector_all_0.4x.png?direct&1200| Vergleich querySelector() vs. querySelectorAll()}}
Als Erstes selektieren wir die Elemente im DOM. Was passiert, wenn wir ''querySelector()'' verwenden?
const panel = document.querySelector('.panel');
const btn = document.querySelector('.accordion-btn');
console.log(panel); // →
...
NUR das erste!
console.log(btn); // → NUR das erste!
\\
''querySelector()'' gibt immer nur das **erste** passende Element zurück. Für ein Accordion mit vier Fragen reicht das nicht – wir brauchen alle.
Die Lösung: ''querySelectorAll()'' gibt **alle** passenden Elemente zurück:
const panels = document.querySelectorAll('.panel');
const buttons = document.querySelectorAll('.accordion-btn');
console.log(panels); // → NodeList(4) [p.panel, p.panel, p.panel, p.panel]
console.log(buttons); // → NodeList(4) [button.accordion-btn, ...]
\\
{{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-05_at_13.11.05.png?direct&600|}}
===== Was ist eine NodeList? =====
''querySelectorAll()'' gibt kein gewöhnliches Array zurück, sondern eine **NodeList** – eine listenartige Sammlung von DOM-Elementen.
Was eine NodeList **kann:**
* Per Index auf eine einzelnes Element zugreifen: ''list[0]'', ''list[1]'' ...
* Wieviele Elemente hat es davon?: ''list.length''
* ''forEach()'' – über alle Elemente iterieren
Was eine NodeList standardmässig **nicht** kann (anders als ein echtes Array):
* ''.map()'', ''.filter()'', ''.reduce()''
const buttons = document.querySelectorAll('.accordion-btn');
console.log(buttons.length); // → 4
console.log(buttons[0]); // →
===== Schritt 3: forEach() – EventListener auf alle Buttons setzen =====
{{:de:modul:m291:learningunits:lu05:theorie:eventlistner_0.5x.png?direct&500| Eventlistener Symbolbild}}
Auf einen **einzelnen Button** einen EventListener setzen würde so aussehen:
button.addEventListener('click', () => {
console.log('Geklickt!');
});
Da wir aber eine NodeList haben, iterieren wir mit ''forEach()'' über alle Elemente und setzen auf jedem einen Listener:
buttons.forEach(b => {
b.addEventListener('click', (e) => {
console.log('Geklickt:', e.target);
});
});
Öffnen Sie die DevTools-Console und klicken Sie auf verschiedene Buttons. ''e.target'' sollte immer den jeweiligen geklickten Button ausgeben.
===== Schritt 4: Das richtige Panel finden – DOM Traversal =====
{{:de:modul:m291:learningunits:lu05:theorie:siblings.jpg?direct&600|}}
Wir haben jetzt EventListener auf allen Buttons. Wenn ein Button geklickt wird, müssen wir das zugehörige ''
'' öffnen. Die Frage ist: **Wie wissen wir, welches Panel zu welchem Button gehört?** –> Button und Panel sind Geschwister im HTML:
{{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-08_at_23.15.46.png?direct&600|}}
==== DOM Traversal mit nextElementSibling ====
Die sauberere Lösung nutzt die **Struktur des DOMs** selbst. Im HTML ist jedes Panel das direkte Geschwister-Element des zugehörigen Buttons:
← Button
...
← direkt daneben: das Panel
Mit ''nextElementSibling'' navigieren wir direkt vom geklickten Button zu seinem Panel:
buttons.forEach((b) => {
b.addEventListener('click', (e) => {
const panelElement = e.target.nextElementSibling; // ✅ direkt das zugehörige Panel
panelElement.classList.add('open');
});
});
==== Warum nicht einfach nextSibling? ====
Der erste Versuch mit ''nextSibling'' scheitert:
buttons.forEach((b) => {
b.addEventListener('click', (e) => {
const panelElement = e.target.nextSibling;
panelElement.classList.add('open'); // ❌ Fehler in der Console!
console.log(e.target.nextSibling); // → #text (ein Zeilenumbruch!)
});
});
**Warum?** Im HTML-Quellcode sind Zeilenumbrüche und Leerzeichen zwischen Tags eigenständige DOM-Nodes (sog. **Text-Nodes**). ''nextSibling'' gibt den allernächsten Node zurück – das ist der Zeilenumbruch nach ''