JavaScript und das DOM/Was ist das DOM
Das DOM ist eigentlich eine Schnittstelle zwischen JavaScript und HTML-Dokumenten, der Name Document Object Model bezieht sich auf das zugrundeliegende Objektmodell.
Eine Webseite liegt dem Browser zunächst als bloßer Text, der mit der Auszeichnungssprache HTML formatiert ist, vor. Noch während der Browser den Code über das Netz empfängt, verarbeitet er ihn Stück für Stück. Diese Aufgabe übernimmt der sogenannte Parser (englisch parse = einen Satz in seine grammatikalischen Einzelteile zerlegen).
Der Parser überführt den HTML-Code in eine Objektstruktur, die dann im Arbeitsspeicher vorgehalten wird. Diese Objektstruktur besteht aus verschachtelten Knoten, die in einer Baumstruktur angeordnet sind.
Der Browser nutzt für alle weiteren Operationen diese Objektstruktur, nicht den HTML-Quellcode, an dem der Webautor üblicherweise arbeitet. Insbesondere CSS und JavaScript beziehen sich nicht auf den HTML-Code als Text, sondern auf den entsprechenden Elementbaum im Speicher.
Dieses Tutorial zeigt, wie du mit JavaScript nun beliebige Elemente im Elementbaum ansprechen und diese verändern, entfernen und mit Anwenderereignissen verknüpfen kannst.
Inhaltsverzeichnis
Die einzelnen Bestandteile einer solchen Baumstruktur werden als Knoten bezeichnet. Das zentrale Objekt des DOM ist deshalb das Node-Objekt (node = Knoten). Es gibt verschiedene Knotentypen. Innerhalb eines gewöhnlichen HTML-Dokuments gibt es auf jeden Fall drei wichtige Knotentypen:
- Elementknoten
- Attributknoten
- Textknoten
DOM-Methoden
Dieser Elementbaum ist jedoch nicht statisch, sondern kann durch DOM-Manipulation ausgelesen, verändert und erweitert werden.
Ausgehend von einem ermittelten Elementknoten lässt sich dann schnell auf dessen Attribute und Inhalt zugreifen.
Elementknoten ansprechen
Erinnerst du dich noch an unser "Hallo Welt"-Beispiel? Die Nachteile einer Textausgabe mittels alert
sind vor allem die Speicherbelastung des Browsers sowie die mangelnde Gestaltungsmöglichkeit des Ausgabefensters.
<output id="info"></output>
<script>
'use strict';
let text = 'Hallo Welt!';
document.getElementById('info').innerText = text;
</script>
Das Script ist nun in eine Webseite eingebunden. Teil dieser Webseite ist ein (noch) leeres output-Element mit der id info
.
Anstelle der Ausgabe mit alert
wird mit document.getElementById
das output-Element über seine id info
angesprochen und mit innerText mit dem auszugebenden Text gefüllt.
Ein weiterer Vorteil dieser Methode ist die Möglichkeit den Ausgabetext beliebig mit CSS zu gestalten.
Dies war die früher übliche Methode, mit JavaScript auf einzelne Elemente zuzugreifen. Da man nicht genau wusste, welche Elemente später als Zugriffspunkte benötigt würden, wurden sicherheitshalber viele id-Attribute vergeben, die das HTML-Markup (womöglich noch in Verbindung mit präsentationsbezogenen Klassennamen ) unnötig aufblähten.
- Versuche semantisches Markup zu verwenden.
- Verwende die passenden Elemente und nutze deren browsereigene Funktionalität!
- Setze ids und Klassennamen nur sparsam ein.
Da diese Art des Elementzugriffs eigentlich umständlich war, entwickelten Frameworks wie jQuery eine Methode, die den direkten Zugriff auf Elemente über ihren Tag-Namen oder über Klassen und Attribute ermöglichte. Diese querySelector-Methode wurde in natives JavaScript übernommen und wird von allen Browsern verstanden.
Im Folgenden verwenden wir die vielseitigere querySelector()-Methode. Sie benötigt als Parameter einen „normalen“ CSS-Selektor und ist daher vielseitiger als das traditionelle getElementById().
<output></output>
<script>
'use strict';
let text = 'Hallo Welt!';
document.querySelector('output').innerText = text;
</script>
Das (noch) leere output-Element wird über die querySelector()-Methode angesprochen und mit innerText mit dem auszugebenden Text gefüllt.
document.getElementById()
, das nur Elemente anspricht, die ein eindeutiges, unverwechselbares id-Universalattribut besitzen, sucht die querySelector-Methode nach den im CSS üblichen Selektoren wie Elemente, Klassen und IDs, aber auch ARIA-Attributen, wie wir später sehen werden.Sie spricht das erste gefundene Element an. Mit QuerySelectorAll kann man alle vorhandenen Vorkommen ansprechen.
Mit diesen Methoden kann man auf das DOM (und dessen Elementknoten) einer Webseite zugreifen:
- querySelector: gibt das erste Element zurück, das dem angegebenen CSS-Selektor entspricht
- querySelectorAll: gibt eine Liste von Elementen zurück, die dem angegebenen CSS-Selektor entsprechen
- getElementsByClassName: gibt eine Liste von Elementen zurück, die der angegebenen Klasse entsprechen
- getElementById: gibt das Element zurück, das die angegebene id besitzt.
- getElementsByTagName: gibt eine Liste von Elementen zurück, die den angegebenen Namen haben.
Textknoten ansprechen
Die meisten Elemente wie Überschriften, Absätze, Links und Buttons enthalten neben möglichen Kind-Elementen noch Textknoten mit dem eigentlichen Inhalt der Webseite.
Im oberen Beispiel wurde dieser Textknoten mit innerText verändert:
document.querySelector('#info').innerText = text;
Des Weiteren existiert mit innerHTML eine weitere Methode, mit der nicht nur Text, sondern sogar weitere HTML-Kindelemente eingefügt werden können.
document.querySelector('#info').innerHTML = '<strong>' + text + '</strong>';
In diesem Beispiel wird die Variable text
von zwei Zeichenketten umschlossen, in denen sich das Markup für ein strong-Element befindet. Wenn der Code ausgeführt wird, ändert sich nicht nur der Textknoten, sondern es wird ein strong-Element in den Elementbaum eingefügt.
→ JavaScript/Tutorials/Cross Site Scripting
mehrere Elemente gleichzeitig ansprechen
Die querySelector()-Methode gibt das erste Element, auf den der Selektor zutrifft, zurück - alle anderen werden ignoriert. Um mehrere Elemente zu selektieren, kann man die querySelectorAll()-Methode verwenden.
document.querySelectorAll('article img') // alle img-Elemente innerhalb von article
document.querySelectorAll('h2, h3') // alle h2 + h3 Überschriften
document.querySelectorAll('input[type="number"]') //alle inputs mit type="number"
document.querySelectorAll('tr:nth-child(odd)') // alle Tabellenreihen mit ungeradem Index
Die querySelectorAll()-Methode gibt nun eine statische NodeList zurück. Diese ist trotz aller Ähnlichkeit kein Array, kann aber in aktuellen Browsern mit einer for...of-Schleife durchlaufen werden. Darüber hinaus unterstützen die Browser auch von Arrays bekannte forEach-Methode, auch wenn die DOM-Spezifikation das nicht vorschreibt. Die mit allen Browsern kompatible Lösung ist, eine NodeList mit einer Zählschleife zu durchlaufen:
const elements = document.querySelectorAll('article > .beispiel');
for (let index = 0; index < elements.length; index++) {
elements[index].classList.add('geändert');
}
Einfacher ist es mit einer for...of-Schleife:
document.getElementById('changer').addEventListener('click', changeClasses);
function changeClasses() {
const elements = document.querySelectorAll('article > .beispiel');
for (let element of elements)
element.classList.add('geändert');
});
}
Im Beispiel werden bei click auf den Button alle Elemente mit der Klasse .beispiel
, die direkte Kindelemente eines <article>
sind, ermittelt. Die NodeList mit dem Suchergebnis wird in elements
gespeichert. NodeList-Objekte sind iterierbar, deshalb kann man die for...of-Schleife nutzen, um die gefundenen Elemente nacheinander zu verarbeiten. Im Schleifenrumpf erhalten die Suchtreffer mit classList.add eine weitere Klasse geändert
.
Besonderheiten
Whitespace
Wie im oberen Abschnitt schon erwähnt, sind Webseiten Text-Dateien, die zur besseren Übersicht oft durch Zeilenumbrüche und einrückende Leerzeichen formatiert werden. Dies stellt Sie manchmal vor ein Problem, da folgender Code nicht wie oben dargestellt geparst wird:
<main>
<h1>Überschrift</h1>
<p>
Textinhalt
<img src="bild.jpg" alt="">
</p>
<!-- Hier ist ein Kommentar -->
</main>
Tatsächlich ist es aber so, dass auch der Whitespace zwischen den HTML Elementen Text darstellt und in Form von Textknoten im DOM abgelegt wird. In Wirklichkeit sieht das DOM also so aus:
Diese Weißräume können nützlich sein. Ein HTML wie das nachfolgende zeigt eine Leerstelle zwischen Hallo und Welt, weil sich zwischen den beiden <span>
-Elementen ein Textknoten befindet, der einen Zeilenumbruch und zwei Leerstellen enthält.
<main>
<span>Hallo</span>
<span>Welt</span>
</main>
Oft wird Whitespace vom Browser aber auch tatsächlich ignoriert. Was ist zum Beispiel mit dem Whitespace-Bereich zwischen den <main>
- und <span>
-Tags? Wenn jegliches Whitespace als ein Leerzeichen dargestellt würde, dann müsste das Wort "Hallo" um ein Leerzeichen eingerückt angezeigt werden. Das wird es aber nicht. Grund dafür ist, dass Leerraum am Beginn und am Ende eines HTML Elements nicht dargestellt wird.
Das vorige Beispiel verwendete einen Inline-Kontext zur Darstellung (weil das die normale Anzeigeart von <span>
-Elementen ist), aber wenn Sie Blockelemente wie <div>
verwenden, entsteht ein Block-Kontext. Hier gelten weitere Regeln.
<main>
<div>Hallo</div> Du
<div>schöne</div>
<div>Welt</div>
</main>
Die Ausgabe wird 4 Zeilen enthalten:
Hallo Du schöne Welt
Das Whitespace zwischen den beiden letzten <div>
-Elementen wird unterdrückt, weil ein Block-Kontext vorliegt.
Whitespace kann aber auch stören. Wenn Sie eine Toolbar mit vielen nebeneinanderliegenden Buttons erzeugen möchten, und jeden Button auf eine eigene Zeile schreiben, würde der Whitespace der Zeilenumbrüche jeweils als eine störende Leerstelle zwischen den Buttons angezeigt.
Um das zu lösen, könnten Sie alle Buttons nebeneinander ins HTML schreiben. Oft genug wird eine solche Toolbar auch per JavaScript erzeugt, und wenn Sie die Buttons einfach nacheinander in ein <div>
einsetzen, befinden sich keine Textknoten zwischen ihnen.
Wenn Sie die Toolbar aber als HTML im Editor erstellen, ist diese Notation schwer lesbar. Früher wurde empfohlen, die schließende spitze Klammer des </button>
-Tags in die Folgezeile zu schreiben, direkt vor das nächste <button>
-Tag. Das ist aber fehleranfällig. Es gibt auch den Vorschlag, den Whitespace in einen HTML Kommentar einzuschließen. Das ist auch nicht schön. Die korrekte Lösung ist, den standardmäßigen Inline-Kontext durch einen anderen zu ersetzen, indem Sie das Containerelement für die Buttons als Flexbox oder Grid darstellen.
Baumreihenfolge
Der Begriff der Baumreihenfolge (tree order) der Elemente im DOM ist zuweilen von Bedeutung. Es gibt in der Informatik drei wichtige Algorithmen, um eine Baumstruktur, wie sie das DOM darstellt, zu durchlaufen. Einer davon ist die Tiefensuche (englich pre-order traversal), bei der für jeden Knoten im Baum der Nachfolger in der Durchlaufreihenfolge so bestimmt wird, dass zunächst geschaut wird, ob er Kindelemente hat. Wenn ja, wird mit dem ersten Kindelement fortgesetzt. Erst, wenn alle Kindelemente durchlaufen sind, wird mit seinen Geschwisterelementen weitergemacht.
Siehe auch
- Primitiver Website-Builder (Neue Knoten erzeugen und in Baumstruktur einhängen)