Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

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:36] gkochde:modul:m291:learningunits:lu05:theorie:a_dom_traversal [2026/03/08 23:24] (aktuell) gkoch
Zeile 7: Zeile 7:
  
 <WRAP center round box 80%> <WRAP center round box 80%>
-{{ :de:modul:m291:learningunits:lu05:theorie:faq-toggle.gif?nolink&800 |FAQ Accordion}}+{{ :de:modul:m291:learningunits:lu05:theorie:faq-toggle.gif?nolink |FAQ Accordion}}
 </WRAP> </WRAP>
  
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 ''.panel'' bekommt eine zweite Klasse ''.open'' sobald es geklickt wird.): 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&800 |}}+{{:de:modul:m291:learningunits:lu05:theorie:faq-class-toggle.gif?nolink |}}
 </WRAP> </WRAP>
  
 ===== Schritt 2: querySelector vs. querySelectorAll ===== ===== Schritt 2: querySelector vs. querySelectorAll =====
  
-{{:de:modul:m291:learningunits:lu05:theorie:queryselector_all_0.4x.png?direct&1100| Vergleich 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? Als Erstes selektieren wir die Elemente im DOM. Was passiert, wenn wir ''querySelector()'' verwenden?
Zeile 177: Zeile 177:
  
  
-===== Schritt 4: DOM Traversal – nextSibling vs. nextElementSibling =====+===== Schritt 4: Das richtige Panel finden – DOM Traversal =====
  
 {{:de:modul:m291:learningunits:lu05:theorie:siblings.jpg?direct&600|}} {{:de:modul:m291:learningunits:lu05:theorie:siblings.jpg?direct&600|}}
  
-Wenn ein Button geklickt wird, müssen wir zum benachbarten ''<p class="panel">'' navigierenDiese Technik nennt man **DOM Traversal** – das gezielte Durchqueren des DOM-Baums.+Wir haben jetzt EventListener auf allen Buttons. Wenn ein Button geklickt wird, müssen wir das zugehörige ''<p class="panel">'' öffnenDie Frage ist: **Wie wissen wir, welches Panel zu welchem Button gehört?** –> Button und Panel sind Geschwister im HTML:
  
-Der naheliegende erste Versuch mit ''nextSibling'' schlägt fehl:+{{: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: 
 +<WRAP center round box 80%> 
 +<code html> 
 +<div class="accordion-item"> 
 +  <button class="accordion-btn">...</button>   ← Button 
 +  <p class="panel">...</p>                     ← direkt daneben: das Panel 
 +</div> 
 +</code> 
 +</WRAP> 
 + 
 +Mit ''nextElementSibling'' navigieren wir direkt vom geklickten Button zu seinem Panel:
  
 <WRAP center round box 80%> <WRAP center round box 80%>
Zeile 189: Zeile 203:
 buttons.forEach((b) => { buttons.forEach((b) => {
   b.addEventListener('click', (e) => {   b.addEventListener('click', (e) => {
-    const panelElement = e.target.nextSibling+    const panelElement = e.target.nextElementSibling// ✅ direkt das zugehörige Panel 
-    panelElement.classList.add('open'); // ❌ Fehler in der Console! +    panelElement.classList.add('open');
-    console.log(e.target.nextSibling);  // → #text  (ein Zeilenumbruch!)+
   });   });
 }); });
Zeile 197: Zeile 210:
 </WRAP> </WRAP>
  
-**Warum klappt das nicht?** 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 in unserem Fall der Zeilenumbruch nach ''<button>'', kein HTML-Element.+==== Warum nicht einfach nextSibling==== 
 + 
 +Der erste Versuch mit ''nextSibling'' scheitert:
  
-Die richtige Methode: ''nextElementSibling'' überspringt Text-Nodes und gibt das nächste **HTML-Element** zurück: 
 <WRAP center round box 80%> <WRAP center round box 80%>
 <code javascript> <code javascript>
 buttons.forEach((b) => { buttons.forEach((b) => {
   b.addEventListener('click', (e) => {   b.addEventListener('click', (e) => {
-    const panelElement = e.target.nextElementSibling// ✅ korrekt +    const panelElement = e.target.nextSibling
-    panelElement.classList.add('open');+    panelElement.classList.add('open'); // ❌ Fehler in der Console! 
 +    console.log(e.target.nextSibling);  // → #text  (ein Zeilenumbruch!)
   });   });
 }); });
Zeile 211: Zeile 226:
 </WRAP> </WRAP>
  
-Testen Sie: Alle Panels sollten sich jetzt öffnen lassen – aber noch nicht schliessen. +**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 ''<button>'', kein HTML-Element''nextElementSibling'' überspringt Text-Nodes und gibt immer das nächste **HTML-Element** zurück.
- +
-==== Übersicht: Traversal-Eigenschaften ====+
  
 <WRAP center round box 80%> <WRAP center round box 80%>
Zeile 224: Zeile 237:
 </WRAP> </WRAP>
  
-===== Schritt 5: Alle anderen Panels schliessen =====+Testen Sie: Alle Panels sollten sich jetzt öffnen lassen – aber noch nicht schliessen. 
 + 
 +===== Schritt 5 & 6Zum finalen Script =====
  
 {{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-08_at_18.28.50.png?direct&600| Ein einziges Panel soll offen sein.}} {{:de:modul:m291:learningunits:lu05:theorie:screenshot_2026-03-08_at_18.28.50.png?direct&600| Ein einziges Panel soll offen sein.}}
  
-Beim klassischen Accordion soll immer nur ein Panel offen sein. Vor dem Öffnen des geklickten Panels schliessen wir deshalb alle:+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 80%> +  - Alle anderen Panels schliessen, wenn eines geöffnet wird 
-<code javascript> +  - Ein geöffnetes Panel beim erneuten Klick wieder schliessen (Toggle
-buttons.forEach((b=> { +  - Sicherstellen, dass wir immer den ''<button>'' ansprechen – nicht ein Kind-Element davon
-  b.addEventListener('click', (e) =+
-    const panelElement = e.target.nextElementSibling;+
  
-    // Alle Panels schliessen +==== Problem: e.target kann das falsche Element sein ====
-    buttons.forEach((andererBtn) => { +
-      andererBtn.nextElementSibling.classList.remove('open'); +
-      andererBtn.setAttribute('aria-expanded', 'false'); +
-    });+
  
-    // Geklicktes Panel öffnen +Wenn der Button ein Icon enthält (bei uns: das ''+'' und ''−'' via ''::after''), kann ein Klick genau auf dieses Icon landen. Dann zeigt ''e.target'' auf das Icon – nicht auf den Button selbst.
-    panelElement.classList.add('open'); +
-  }); +
-}); +
-</code> +
-</WRAP> +
- +
-Testen Sie: Ein Panel öffnen schliesst nun alle anderen. +
- +
-Das ''setAttribute('aria-expanded', ...)'' erklären wir ausführlich in den folgenden Seiten. +
- +
- +
-===== Schritt 6e.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 ''e.target'' auf das Icon zeigen, nicht auf den Button selbst.+
  
 <WRAP center round box 80%> <WRAP center round box 80%>
Zeile 262: Zeile 257:
 | ''event.target'' | Das Element, das das Event **ursprünglich ausgelöst** hat – kann ein Kind-Element (wie ein Icon) sein | | ''event.target'' | Das Element, das das Event **ursprünglich ausgelöst** hat – kann ein Kind-Element (wie ein Icon) sein |
 | ''event.currentTarget'' | Das Element, an dem der **EventListener registriert** wurde – immer der Button | | ''event.currentTarget'' | Das Element, an dem der **EventListener registriert** wurde – immer der Button |
 +</WRAP>
 +
 +Die Lösung: Wir lesen den Button immer via ''e.currentTarget'' aus und speichern ihn in einer Variable. Damit haben wir auch die stabile Basis für ''nextElementSibling''.
 +
 +==== Das finale Script ====
  
 +<WRAP center round box 80%>
 <code javascript> <code javascript>
-// Im finalen Projekt: Button enthält ein SVG-Icon via CSS ::after +const buttons document.querySelectorAll('.accordion-btn');
-// <button class="accordion-btn">Frage...</button>  +  .accordion-btn::after { content: url(...}+
  
-buttons.forEach((b) => { +buttons.forEach((button) => { 
-  b.addEventListener('click', (e) => {+  button.addEventListener('click', (e) => { 
 +    const btn          = e.currentTarget;           // immer der <button> 
 +    const panelElement = btn.nextElementSibling;    // direkt das zugehörige Panel 
 +    const panelIsOpen  = panelElement.classList.contains('open'); // aktuellen Zustand lesen
  
-    console.log(e.target); +    // Alle Panels schliessen 
-    // → Kann das ::after-Pseudo-Element oder ein inneres Element sein +    buttons.forEach((andererBtn) => { 
- +      andererBtn.nextElementSibling.classList.remove('open'); 
-    console.log(e.currentTarget); +      andererBtn.setAttribute('aria-expanded', 'false'); 
-    // → Immer der <button> – weil dort addEventListener(registriert wurde ✅+    });
  
 +    // Dieses Panel öffnen – aber nur wenn es vorher geschlossen war
 +    if (!panelIsOpen) {
 +      panelElement.classList.add('open');
 +      btn.setAttribute('aria-expanded', 'true');
 +    }
   });   });
 }); });
Zeile 281: Zeile 289:
 </WRAP> </WRAP>
  
 +**Was passiert bei jedem Klick?**
  
 +  - ''e.currentTarget'' gibt uns sicher den ''<button>'', egal ob auf Text oder Icon geklickt wurde
 +  - ''btn.nextElementSibling'' gibt das direkt zugehörige Panel zurück
 +  - ''classList.contains('open')'' liest den aktuellen Zustand, bevor wir alles schliessen
 +  - Das innere ''forEach()'' schliesst alle Panels und setzt ''aria-expanded'' auf ''false''
 +  - Die ''if''-Bedingung öffnet das geklickte Panel nur dann, wenn es vorher zu war – so funktioniert auch der Toggle (zweimal klicken schliesst)
 +
 +Das ''setAttribute('aria-expanded', ...)'' erklären wir ausführlich in den folgenden Seiten.
  
  
  • de/modul/m291/learningunits/lu05/theorie/a_dom_traversal.1772991410.txt.gz
  • Zuletzt geändert: 2026/03/08 18:36
  • von gkoch