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 17:02] – 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 58: | Zeile 58: | ||
| <script src=" | <script src=" | ||
| </ | </ | ||
| + | |||
| + | \\ | ||
| + | Anschliessend sollte es so aussehen im Browser: | ||
| + | {{: | ||
| </ | </ | ||
| Zeile 66: | 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 81: | Zeile 85: | ||
| </ | </ | ||
| Was macht dieser Code (später)? | Was macht dieser Code (später)? | ||
| - | Er blendet nicht aktive Antworten aus und blended das aktive Element ein: | + | 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 80%> | ||
| <code javascript> | <code javascript> | ||
| const panel = document.querySelector(' | const panel = document.querySelector(' | ||
| Zeile 97: | Zeile 103: | ||
| </ | </ | ||
| + | \\ | ||
| '' | '' | ||
| + | </ | ||
| Die Lösung: '' | Die Lösung: '' | ||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| const panels | const panels | ||
| Zeile 109: | Zeile 118: | ||
| </ | </ | ||
| - | ^ Methode ^ Gibt zurück ^ Einsatz ^ | + | \\ |
| - | | '' | + | |
| - | | '' | + | {{: |
| + | </ | ||
| Zeile 119: | Zeile 130: | ||
| Was eine NodeList **kann:** | Was eine NodeList **kann:** | ||
| - | * Per Index zugreifen: '' | + | * Per Index auf eine einzelnes Element |
| - | * Länge auslesen: '' | + | * Wieviele Elemente hat es davon?: '' |
| * '' | * '' | ||
| Zeile 126: | Zeile 137: | ||
| * '' | * '' | ||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| const buttons = document.querySelectorAll(' | const buttons = document.querySelectorAll(' | ||
| Zeile 133: | Zeile 145: | ||
| </ | </ | ||
| + | </ | ||
| ===== Schritt 3: forEach() – EventListener auf alle Buttons setzen ===== | ===== Schritt 3: forEach() – EventListener auf alle Buttons setzen ===== | ||
| - | Auf einen einzelnen Button einen EventListener setzen würde so aussehen: | + | {{:de: |
| + | Auf einen **einzelnen Button** einen EventListener setzen würde so aussehen: | ||
| + | |||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| button.addEventListener(' | button.addEventListener(' | ||
| Zeile 144: | Zeile 160: | ||
| }); | }); | ||
| </ | </ | ||
| + | </ | ||
| Da wir aber eine NodeList haben, iterieren wir mit '' | Da wir aber eine NodeList haben, iterieren wir mit '' | ||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| buttons.forEach(b => { | buttons.forEach(b => { | ||
| Zeile 154: | Zeile 172: | ||
| }); | }); | ||
| </ | </ | ||
| + | </ | ||
| Öffnen Sie die DevTools-Console und klicken Sie auf verschiedene Buttons. '' | Öffnen Sie die DevTools-Console und klicken Sie auf verschiedene Buttons. '' | ||
| - | ===== 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. | ||
| + | |||
| + | {{: | ||
| + | |||
| + | ==== DOM Traversal | ||
| + | |||
| + | Die sauberere Lösung nutzt die **Struktur des DOMs** selbst. Im HTML ist jedes Panel das direkte Geschwister-Element | ||
| + | <WRAP center round box 80%> | ||
| + | <code html> | ||
| + | <div class=" | ||
| + | <button class=" | ||
| + | <p class=" | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| - | Der naheliegende erste Versuch mit '' | + | 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); | + | |
| }); | }); | ||
| }); | }); | ||
| </ | </ | ||
| + | </ | ||
| - | **Warum klappt das nicht?** Im HTML-Quellcode sind Zeilenumbrüche und Leerzeichen zwischen Tags eigenständige DOM-Nodes (sog. **Text-Nodes**). '' | + | ==== Warum nicht einfach |
| - | Die richtige Methode: | + | Der erste Versuch mit '' |
| + | <WRAP center round box 80%> | ||
| <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); | ||
| }); | }); | ||
| }); | }); | ||
| </ | </ | ||
| + | </ | ||
| - | 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%> | ||
| ^ Eigenschaft ^ Beschreibung ^ | ^ Eigenschaft ^ Beschreibung ^ | ||
| | '' | | '' | ||
| Zeile 197: | Zeile 235: | ||
| | '' | | '' | ||
| | '' | | '' | ||
| + | </ | ||
| - | ===== Schritt 5: Alle anderen | + | Testen Sie: Alle Panels |
| - | Beim klassischen Accordion soll immer nur ein Panel offen sein. Vor dem Öffnen des geklickten Panels schliessen wir deshalb alle: | + | ===== Schritt 5 & 6: Zum finalen Script ===== |
| - | <code javascript> | + | {{: |
| - | buttons.forEach((b) => { | + | |
| - | b.addEventListener(' | + | |
| - | const panelElement = e.target.nextElementSibling; | + | |
| - | // Alle Panels schliessen | + | Jetzt bauen wir den Code Schritt für Schritt zum finalen Script aus. Drei Dinge müssen noch gelöst werden: |
| - | buttons.forEach((andererBtn) => { | + | |
| - | andererBtn.nextElementSibling.classList.remove(' | + | |
| - | andererBtn.setAttribute(' | + | |
| - | }); | + | |
| - | // Geklicktes | + | - Alle anderen Panels schliessen, wenn eines geöffnet wird |
| - | panelElement.classList.add(' | + | - Ein geöffnetes |
| - | | + | |
| - | }); | + | |
| - | </code> | + | |
| - | Testen Sie: Ein Panel öffnen schliesst nun alle anderen. | + | ==== Problem: e.target kann das falsche Element sein ==== |
| - | Das '' | + | Wenn der Button ein Icon enthält (bei uns: das '' |
| - | + | ||
| - | + | ||
| - | ===== Schritt 6: e.target vs. e.currentTarget ===== | + | |
| - | + | ||
| - | Unser Code funktioniert – hat aber noch eine versteckte Schwachstelle. Sobald wir Icons innerhalb des Buttons hinzufügen (z.B. als Pseudo-Elemente via CSS, was wir beim Styling noch tun), kann '' | + | |
| + | <WRAP center round box 80%> | ||
| ^ Eigenschaft ^ Beschreibung ^ | ^ Eigenschaft ^ Beschreibung ^ | ||
| - | | '' | + | | '' |
| | '' | | '' | ||
| + | </ | ||
| - | <code javascript> | + | Die Lösung: Wir lesen den Button |
| - | // Im finalen Projekt: Button | + | |
| - | // <button class=" | + | |
| - | + | ||
| - | buttons.forEach((b) => { | + | |
| - | b.addEventListener('click', (e) => { | + | |
| - | + | ||
| - | console.log(e.target); | + | |
| - | // → Kann das :: | + | |
| - | + | ||
| - | console.log(e.currentTarget); | + | |
| - | // → Immer der < | + | |
| - | + | ||
| - | }); | + | |
| - | }); | + | |
| - | </ | + | |
| - | + | ||
| - | ==== Das finale Script (script.js) ==== | + | |
| - | Im abgeschlossenen Accordion-Projekt sieht das vollständige | + | ==== Das finale |
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| const buttons = document.querySelectorAll(' | const buttons = document.querySelectorAll(' | ||
| Zeile 258: | 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 276: | Zeile 287: | ||
| }); | }); | ||
| </ | </ | ||
| - | |||
| - | <WRAP important> | ||
| - | **Faustregel: | ||
| </ | </ | ||
| + | |||
| + | **Was passiert bei jedem Klick?** | ||
| + | |||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - Das innere '' | ||
| + | - Die '' | ||
| + | |||
| + | Das '' | ||