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:44] – 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 123: | Zeile 123: | ||
| </ | </ | ||
| - | |||
| - | |||
| - | ^ Methode ^ Gibt zurück ^ Einsatz ^ | ||
| - | | '' | ||
| - | | '' | ||
| Zeile 135: | 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 142: | Zeile 137: | ||
| * '' | * '' | ||
| + | <WRAP center round box 80%> | ||
| <code javascript> | <code javascript> | ||
| const buttons = document.querySelectorAll(' | const buttons = document.querySelectorAll(' | ||
| Zeile 149: | 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 160: | 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 170: | 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 ''< | + | {{: |
| - | Der naheliegende erste Versuch mit '' | + | Wir haben jetzt EventListener auf allen Buttons. Wenn ein Button geklickt wird, müssen wir das zugehörige |
| + | {{: | ||
| + | |||
| + | ==== 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); | + | |
| }); | }); | ||
| }); | }); | ||
| </ | </ | ||
| + | </ | ||
| - | **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 213: | 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 274: | 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 292: | Zeile 287: | ||
| }); | }); | ||
| </ | </ | ||
| - | |||
| - | <WRAP important> | ||
| - | **Faustregel: | ||
| </ | </ | ||
| + | |||
| + | **Was passiert bei jedem Klick?** | ||
| + | |||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - Das innere '' | ||
| + | - Die '' | ||
| + | |||
| + | Das '' | ||