Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen Revision Vorhergehende Überarbeitung Nächste Überarbeitung | Vorhergehende Überarbeitung | ||
| de:modul:m291:learningunits:lu05:theorie:a_dom_traversal [2026/03/08 18:30] – gkoch | de:modul:m291:learningunits:lu05:theorie:a_dom_traversal [2026/03/08 23:24] (aktuell) – gkoch | ||
|---|---|---|---|
| Zeile 6: | Zeile 6: | ||
| In diesem Block bauen Sie ein interaktives FAQ Accordion. | In diesem Block bauen Sie ein interaktives FAQ Accordion. | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| - | {{ : | + | {{ : |
| </ | </ | ||
| Laden Sie hier das Figma-File, die Start-HTML und das Readme herunter: | Laden Sie hier das Figma-File, die Start-HTML und das Readme herunter: | ||
| - | <WRAP center round download | + | <WRAP center round download |
| {{ : | {{ : | ||
| </ | </ | ||
| - | <WRAP tip round center | + | <WRAP tip round center |
| **Projektaufbau: | **Projektaufbau: | ||
| {{: | {{: | ||
| Zeile 70: | Zeile 70: | ||
| Bevor wir JavaScript schreiben, definieren wir die zwei möglichen Zustände im CSS. Die Klasse '' | Bevor wir JavaScript schreiben, definieren wir die zwei möglichen Zustände im CSS. Die Klasse '' | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| Dieser Code kommt ins '' | Dieser Code kommt ins '' | ||
| Zeile 86: | Zeile 86: | ||
| Was macht dieser Code (später)? | Was macht dieser Code (später)? | ||
| Er blendet nicht aktive Antworten aus und blended das aktive Element ein (div mit Klasse '' | Er blendet nicht aktive Antworten aus und blended das aktive Element ein (div mit Klasse '' | ||
| - | {{: | + | {{: |
| </ | </ | ||
| ===== Schritt 2: querySelector vs. querySelectorAll ===== | ===== Schritt 2: querySelector vs. querySelectorAll ===== | ||
| - | {{: | + | {{: |
| Als Erstes selektieren wir die Elemente im DOM. Was passiert, wenn wir '' | Als Erstes selektieren wir die Elemente im DOM. Was passiert, wenn wir '' | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| <code javascript> | <code javascript> | ||
| const panel = document.querySelector(' | const panel = document.querySelector(' | ||
| Zeile 109: | Zeile 109: | ||
| Die Lösung: '' | Die Lösung: '' | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| <code javascript> | <code javascript> | ||
| const panels | const panels | ||
| Zeile 137: | Zeile 137: | ||
| * '' | * '' | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| <code javascript> | <code javascript> | ||
| const buttons = document.querySelectorAll(' | const buttons = document.querySelectorAll(' | ||
| Zeile 154: | Zeile 154: | ||
| Auf einen **einzelnen Button** einen EventListener setzen würde so aussehen: | Auf einen **einzelnen Button** einen EventListener setzen würde so aussehen: | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| <code javascript> | <code javascript> | ||
| button.addEventListener(' | button.addEventListener(' | ||
| Zeile 164: | Zeile 164: | ||
| Da wir aber eine NodeList haben, iterieren wir mit '' | Da wir aber eine NodeList haben, iterieren wir mit '' | ||
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| <code javascript> | <code javascript> | ||
| buttons.forEach(b => { | buttons.forEach(b => { | ||
| Zeile 177: | Zeile 177: | ||
| - | ===== Schritt 4: DOM Traversal | + | ===== Schritt 4: Das richtige Panel finden – DOM Traversal ===== |
| {{: | {{: | ||
| - | Wenn ein Button geklickt wird, müssen wir zum benachbarten | + | Wir haben jetzt EventListener auf allen Buttons. |
| - | Der naheliegende erste Versuch mit '' | + | {{:de: |
| - | <WRAP center round box 60%> | + | ==== 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: | ||
| + | <WRAP center round box 80%> | ||
| + | <code html> | ||
| + | <div class=" | ||
| + | <button class=" | ||
| + | <p class=" | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | Mit '' | ||
| + | |||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| buttons.forEach((b) => { | buttons.forEach((b) => { | ||
| b.addEventListener(' | b.addEventListener(' | ||
| - | const panelElement = e.target.nextSibling; | + | const panelElement = e.target.nextElementSibling; // ✅ direkt das zugehörige Panel |
| - | panelElement.classList.add(' | + | panelElement.classList.add(' |
| - | console.log(e.target.nextSibling); | + | |
| }); | }); | ||
| }); | }); | ||
| Zeile 197: | Zeile 210: | ||
| </ | </ | ||
| - | **Warum klappt das nicht?** Im HTML-Quellcode sind Zeilenumbrüche und Leerzeichen zwischen Tags eigenständige DOM-Nodes (sog. **Text-Nodes**). | + | ==== Warum nicht einfach nextSibling? ==== |
| + | |||
| + | Der erste Versuch mit '' | ||
| - | Die richtige Methode: '' | + | <WRAP center round box 80%> |
| - | <WRAP center round box 60%> | + | |
| <code javascript> | <code javascript> | ||
| buttons.forEach((b) => { | buttons.forEach((b) => { | ||
| b.addEventListener(' | b.addEventListener(' | ||
| - | const panelElement = e.target.nextElementSibling; // ✅ korrekt | + | const panelElement = e.target.nextSibling; |
| - | panelElement.classList.add(' | + | panelElement.classList.add(' |
| + | console.log(e.target.nextSibling); | ||
| }); | }); | ||
| }); | }); | ||
| Zeile 211: | Zeile 226: | ||
| </ | </ | ||
| - | Testen Sie: Alle Panels sollten sich jetzt öffnen lassen | + | **Warum?** Im HTML-Quellcode sind Zeilenumbrüche und Leerzeichen zwischen Tags eigenständige DOM-Nodes (sog. **Text-Nodes**). '' |
| - | ==== Übersicht: Traversal-Eigenschaften ==== | + | <WRAP center round box 80%> |
| - | + | ||
| - | <WRAP center round box 60%> | + | |
| ^ Eigenschaft ^ Beschreibung ^ | ^ Eigenschaft ^ Beschreibung ^ | ||
| | '' | | '' | ||
| Zeile 224: | Zeile 237: | ||
| </ | </ | ||
| - | ===== Schritt 5: Alle anderen Panels schliessen | + | Testen Sie: Alle Panels sollten sich jetzt öffnen lassen – aber noch nicht schliessen. |
| + | |||
| + | ===== Schritt 5 & 6: Zum finalen Script | ||
| {{: | {{: | ||
| - | Beim klassischen Accordion soll immer nur ein Panel offen sein. Vor dem Öffnen des geklickten Panels schliessen | + | Jetzt bauen wir den Code Schritt für Schritt zum finalen Script aus. Drei Dinge müssen noch gelöst werden: |
| - | + | ||
| - | <WRAP center round box 60%> | + | |
| - | <code javascript> | + | |
| - | buttons.forEach((b) => { | + | |
| - | b.addEventListener(' | + | |
| - | const panelElement = e.target.nextElementSibling; | + | |
| - | + | ||
| - | // Alle Panels schliessen | + | |
| - | buttons.forEach((andererBtn) => { | + | |
| - | andererBtn.nextElementSibling.classList.remove(' | + | |
| - | andererBtn.setAttribute(' | + | |
| - | }); | + | |
| - | + | ||
| - | // Geklicktes Panel öffnen | + | |
| - | panelElement.classList.add(' | + | |
| - | }); | + | |
| - | }); | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | Testen Sie: Ein Panel öffnen schliesst nun alle anderen. | + | |
| - | + | ||
| - | Das '' | + | |
| + | - Alle anderen Panels schliessen, wenn eines geöffnet wird | ||
| + | - Ein geöffnetes Panel beim erneuten Klick wieder schliessen (Toggle) | ||
| + | - Sicherstellen, | ||
| - | ===== Schritt 6: e.target | + | ==== Problem: e.target |
| - | Unser Code funktioniert – hat aber noch eine versteckte Schwachstelle. Sobald wir Icons innerhalb des Buttons hinzufügen | + | Wenn der Button ein Icon enthält |
| - | <WRAP center round box 60%> | + | <WRAP center round box 80%> |
| ^ Eigenschaft ^ Beschreibung ^ | ^ Eigenschaft ^ Beschreibung ^ | ||
| | '' | | '' | ||
| | '' | | '' | ||
| - | |||
| - | <code javascript> | ||
| - | // Im finalen Projekt: Button enthält ein SVG-Icon via CSS ::after | ||
| - | // <button class=" | ||
| - | |||
| - | buttons.forEach((b) => { | ||
| - | b.addEventListener(' | ||
| - | |||
| - | console.log(e.target); | ||
| - | // → Kann das :: | ||
| - | |||
| - | console.log(e.currentTarget); | ||
| - | // → Immer der < | ||
| - | |||
| - | }); | ||
| - | }); | ||
| - | </ | ||
| </ | </ | ||
| - | ==== Das finale Script (script.js) ==== | + | Die Lösung: Wir lesen den Button immer via '' |
| - | Im abgeschlossenen Accordion-Projekt sieht das vollständige | + | ==== Das finale |
| - | < | + | < |
| <code javascript> | <code javascript> | ||
| const buttons = document.querySelectorAll(' | const buttons = document.querySelectorAll(' | ||
| Zeile 291: | Zeile 269: | ||
| buttons.forEach((button) => { | buttons.forEach((button) => { | ||
| button.addEventListener(' | button.addEventListener(' | ||
| - | const btn = e.currentTarget; | + | const btn = e.currentTarget; |
| - | const panelElement = btn.nextElementSibling; | + | const panelElement = btn.nextElementSibling; |
| - | const panelIsOpen | + | const panelIsOpen |
| // Alle Panels schliessen | // Alle Panels schliessen | ||
| Zeile 311: | Zeile 289: | ||
| </ | </ | ||
| + | **Was passiert bei jedem Klick?** | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - Das innere '' | ||
| + | - Die '' | ||
| + | |||
| + | Das '' | ||