====== 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 ''