DOM - Document Object Model
Browser analysiert HTML Datei und legt die Elemente als Objekte in einen Speicher, das DOM, ab -> “rendern”.
Es besteht aus Knoten, den sogenannten Nodes
Es gibt Element-Knoten, Text-Knoten und Kommentar-Knoten. Knoten können auch verschachtelt sein (z.B. Satz mit einzelnem Wort als Link)
Das oberste Element ist der
documentKnoten: dieser enthält zahlreiche Infos zum Dokument und zahlreiche Methoden um das Dokument zu bearbeiten.
Hint
Erreichbar über .
Beispiele:
console.log(document); // gesamtes Dokument Element ausgeben
console.log(document.body); // body Element des Dokuments
// Zugriff auf beliebige Elemente (sind JS Objekte)
document.getElementById("id"); // => Element
document.getElementByTagName("tagname"); // => HTML-Collection
document.getElementByClassName("classname"); // => HTML-Collection
document.getElementsByName("name"); // => HTML-Collection
Die Methoden können auf alle Elemente angewendet werden und werden dann auf alle innenliegenden Elemente zugreifen. Mit JQuery ist jedoch eine zielgerichtetere Suche nach bestimmten Elementen möglich. Jedoch kamen mit ES6 zwei neue Selektoren hinzu, die wiederum die Anwendung von JQuery nicht mehr nötig machen.
Query-Selector
Neu seit ES6 (von JQuery inspiriert):
document.querySelector("css-syntax"); // => Element
document.querySelectorAll("css-syntax"); // => NodeList (moderne Version von HTML-Collection)
console.log(document.querySelector("h1")); // Element: erstes Match wird zurückgegeben
console.log(document.querySelector("h2"));
console.log(document.querySelectorAll("h2")); // NodeList: alle Matches werden zurückgegeben
console.log(document.querySelectorAll("h2")[1]); // Element an index 1
Content-Zugriff
Zugriff auf Inhalt von Elementen, inklusive Unter-Elementen (wenn vorhanden).
Hint
Zugriffe auf das DOM sind das Nadelöhr für eine Webseite. Daher sollten Zugriffe auf das DOM möglichst minimal gehalten werden, d.h.
vorhanden DOM Element als Variablen ablegen
neue Elemente vorab anfertigen, bevor sie ins DOM geschrieben werden
Mit .innerHTML wird der Content inklusive aller Markups (Element-Tags) geliefert.
let headLine = document.querySelector("h1");
// [element].innerHTML => liefert den Content Text inklusive Markups
console.log(headLine.innerHTML);
// überschreiben vom Text Content
headLine.innerHTML = "<em>Hallo</em> Welt";
console.log(headLine.innerHTML);
Hint
Die ursprüngliche HTML Seite wird durch Änderung nicht verändert. Die Änderung erfährt nur das DOM.
// hinzufügen von Text
headLine.innerHTML = "Achtung: " + headLine.innerHTML;
headLine.innerHTML += "!!!"; // hinten anfügen
// gesamter Content wird geschrieben
headLine.innerHTML += " mit Zusatz <span style=\"display:none\">unsichtbar</span>";
Den gerenderten Content (so wie User es sieht -> Ohne Markups, ohne doppelte Leerzeichen
oder ausgeblendete Inhalte) wird über .innerText sichtbar:
headLine.innerHTML += " mit Zusatz <span style=\"display:none\">unsichtbar</span>";
console.log(headLine.innerHTML);
// Achtung: <em>Hallo</em> Welt!!! mit Zusatz <span style="display:none">unsichtbar</span>
console.log(headLine.innerText); // "Achtung: Hallo Welt!!! mit Zusatz"
Über .textContent werden doppelte Leerzeichen ausgegeben, aber keine Markups
oder ausgeblendete Inhalte.
headLine.innerHTML += " mit Zusatz <span style=\"display:none\">unsichtbar</span>";
console.log(headLine.textContent); // Achtung: Hallo Welt!!! mit Zusatz unsichtbar
Hint
.innerText und .textContent sind performanter, als .innerHTML,
aber enthalten weniger Informationen. Es kommt auf den Anwendungsfall an,
welches Property ausgelesen werden soll.
NodeList
Im Gegensatz zu JQuery, welche die Ergebnis-Elemente als Liste von Objekten zurückgibt, sind auf eine NodeList diese Methoden nicht direkt verfügbar. Die NodeList enthält wiederum diese Objekte.
let listItems = document.querySelectorAll("li");
console.log(listItems);
// Anzahl der selektierten Elemente
console.log(listItems.length);
console.log(listItems.innerHTML); // undefined -> nicht verfügbar
for (let i = 0; i < listItems.length; i++) {
console.log(listItems[i]); // OK
console.log(listItems[i]).innerHTML;
}
Bei nicht vorhandenen Elementen wird null zurückgegeben bzw. ein leeres
NodeList Element. Hier mit einer einfachen Prüfung arbeiten:
// Element nicht vorhanden
let orderedList = document.querySelector("ol");
console.log(orderedList); // null
console.log(orderedList.innerHTML); // TypeError
// prüfe ob Element im DOM vorhanden (Zugriffsfehler vermeiden)
if (orderedList) {
console.log(orderedList.innerHTML);
}
// Bei querySelectorAll
let orderedLists = document.querySelectorAll("ol");
console.log(orderedLists); // empty NodeList
if (orderedList.length) {
// hier nur wenn NodeList nicht leer
}
Globales vs lokales Selektieren
Global: über
documentObjektLokal: über ein Element (innerhalb aller eingeschlossenen Elemente)
// global selektieren (über document)
let myList = document.querySelector("#secondList");
let listItems = document.querySelectorAll("li");
console.log(myList);
console.log(listItems);
// lokales selektieren (innerhalb eines Elements)
listItems = myList.querySelectorAll("li");
console.log(listItems);
DOM-Zugriff auf Class-Attribut
Das class Attribute von Elementen manipulieren. Die className Attribut
ist die performanteste Methode, es gibt auch die Möglichkeit über classList
(DOMTokenList, ein Array-artiges Gebilde) Klassen zu verwalten, welches
weitere Methoden liefert.
/*
Variante 1:
[element].className => speichert Strings
Manipulation über Zeichenkettenverarbeitung
*/
let para = document.querySelector(".absatz");
console.log(para.className);
para.className = "wichtig"; // className überschreiben
para.className += " absatz texte"; // Klassen hinzufügen
/*
Variante 2:
[element].classList
*/
console.log(para.classList); // => DOMTokenList
// Anzahl der am Element anliegeneden Klassen
console.log(para.classList.length);
// Zugriff über Property Access
console.log(para.classList[1]); // "absatz"
}
classList Methoden:
.add()fügt Klassen hinzu (keine doppelten erlaubt)
.remove()entfernt eine Klasse
.replace()ersetzt eine Klasse
.toggle()fügt eine Klasse hinzu wenn nicht vorhanden, sonst wird diese entfernt
.contains()prüft ob eine Klasse einem Element anhängt (alternativ auch über Property Access)
para.classList.add("hinweis"); // Klasse hinzufügen
para.classList.add("hinweis"); // Versuch doppeltes Hinzufügen wird ignoriert
para.classList.remove("texte"); // Klasse entfernen
para.classList.replace("absatz", "new-absatz"); // Klasse ersetzen
para.classList.replace("fehlt", "fehlt-immer-noch");
/*
toggle:
wenn Klasse vorhanden -> wird entfernt
wenn Klasse nicht vorhanden -> wird hinzugefügt
*/
para.classList.toggle("wichtig");
console.log(para.classList);
// auf Klasse prüfen
if (para.classList.contains("hinweis")) {
console.log(para.classList.contains("hinweis")); // true
}
// durch classList iterieren über Property Access
for (let i = 0; i < para.classList.length; i++) {
console.log(para.classList[i]);
}
Zugriff auf Attribute von HTML-Elementen
Content-Zugriff
Über .getAttribute() wird Inhalt eines Attributs geliefert (oder null,
falls Attribut nicht existiert):
let myProgress = document.querySelector("progress");
// Attribute auslesen
// [element].getAttribute(attr_name);
// Rückgabe ist immer ein String
console.log(myProgress.getAttribute("max")); // "10"
console.log(myProgress.getAttribute("value")); // "1"
console.log(myProgress.getAttribute("title")); // null
Über .setAttribute() den Wert eines Attributs ändern oder neue Attribute
hinzufügen:
// Attribute setzen
myProgress.setAttribute("max", 100);
myProgress.setAttribute("value", 8);
myProgress.setAttribute("id", "progress-bar"); // neues Attribut
Über .removeAttribute() ein vorhandenes Attribut entfernen. Ist das Attribut
nicht vorhanden, wird der Befehl ignoriert (kein Fehler):
// Attribut entfernen
myProgress.removeAttribute("id");
console.log(myProgress.getAttribute("id")); // null
Über .hasAttribute() prüfen ob ein Element ein bestimmtes Attribut hat:
// prüfen ob eine Attribut anliegt
console.log(myProgress.hasAttribute("id")); // true
console.log(myProgress.hasAttribute("max")); // false
Über .attributes alle Attribute eines Elements in einer NamedNodeMap
erhalten:
console.log(myProgress.attributes); // NamedNodeMap
// etwas umständlich, aber möglich
console.log(myProgress.attributes.max); // max="100"
console.log(myProgress.attributes.max.nodeValue); // "100"
IDL-Zugriff
IDL … Interface Description Language
Einfacher als Content-Zugriff, allerdings ist der Zugriff nur möglich, wenn das Element als Interface definiert ist.
Vorteile:
einfachere Notation
Der Rückgabewert erfolgt im exakten Datentyp, nicht stets als String
Nachteile:
Funktioniert nur über ein Interface
// IDL-Zugriff
let myProgress = document.querySelector("progress");
// Attribut auslesen
// (direkter Zugriff über Punktnotation anstelle Weg über Methoden)
console.log(myProgress.max); // 100 (number)
console.log(myProgress.value); // 8 (number)
// Attribut setzen
myProgress.max = 8;
myProgress.value = 5;
console.log(myProgress.max); // 8 (number)
console.log(myProgress.value); // 5 (number)
Sonderfall: Boolesche Attribute
Attribute, welche ohne Wert das Verhalten eines Elements beeinflussen
Beispiele: async, defer, disabled, required
Über Content-Zugriff:
let myBtn = document.querySelector("button");
// mit Content-Zugriff
// boolesches Attribut setzen
// doppelte Übergabe nötig (Wert des 2. Arguments jedoch egal)
myBtn.setAttribute("disabled", "disabled");
// boolesches Attribut entfernen
myBtn.removeAttribute("disabled");
// boolesches Attribut toggeln
myBtn.toggleAttribute("disabled"); // hinzufügen
myBtn.toggleAttribute("disabled"); // entfernen
Über IDL-Zugriff (wieder einfacher):
let myBtn = document.querySelector("button");
// mit IDL-Zugriff
myBtn.disabled = true;
myBtn.disabled = false;
Interface Zugriff
Der Content-Zugriff kann nicht auf Interfaces zugreifen, dies ist nur über den IDL-Zugriff möglich.
let eingabeFeld = document.querySelector("#eingabe");
console.log(eingabeFeld.getAttribute("value")); // "Hey"
console.log(eingabeFeld.value); // "Hey"
// Änderung über Content
eingabeFeld.setAttribute("value", "Hey, du da");
console.log(eingabeFeld.getAttribute("value")); // "Hey, du da"
console.log(eingabeFeld.value); // "Hey, du da"
// Änderung über IDL
eingabeFeld.value = "Hoppla";
console.log(eingabeFeld.getAttribute("value")); // "Hey, du da" (hat nicht geklappt)
console.log(eingabeFeld.value); // "Hoppla" (OK)
// Änderung bei Nutzer-Eingabe
eingabeFeld.onchange = function() {
console.log(eingabeFeld.getAttribute("value")); // NOK -> alter Wert
console.log(eingabeFeld.value); // OK -> neuer Wert
};
Fazit:
Bei Nutzereingaben muss immer über IDL gegangen werden –> bei Dokument im DOM
Vorherige Werte lassen sich auch über Content Zugriff geholt –> bei Dokument im Speicher
Events
Auf Vorgänge, die während der Laufzeit auftreten können (z.B. Maus- & Tastatureingaben) kann mit einem anderen Ereignis reagiert werden. Es gibt dafür zwei Möglichkeiten:
Event-Listener
Event-Handler
Important
Um zu verhindern, dass Events mehrfach registriert werden, stets mit dem Event-Handler arbeiten, nicht mit dem Event-Listener.
Event-Listener
eine Funktion wird an das Element (z.B. Button), welches eine Ereignis erfährt, registriert. Tritt das Ereignis ein, wird die Funktion ausgeführt. Es können mehrere Funktionen für das selbe Event für ein Element definiert werden, wobei die Funktionen hintereinander ausgeführt werden.
let inputField = document.querySelector("#eingabe");
function log_msg (msg) {
console.log(msg);
}
// anonyme Funktion als ersten Event-Listener für 'blur' event registrieren
// NACHTEIL: Kann nicht de-registriert werden, da anonyme Funktion
inputField.addEventListener("blur", function() {
console.log("Ich bin nicht mehr selektiert");
});
// anonyme Funktion zweiter Event-Lister für 'blur' event registrieren
// beide Funktionen werden nun ausgeführt beim Eintritt des Events
// NACHTEIL: Kann nicht de-registriert werden, da anonyme Funktion
inputField.addEventListener("blur", function() {
log_msg("Selektiere mich bitte erneut");
});
function onBlurHandler() {
console.log("Ich mach das ...");
}
function onBlurHandlerWithArgs(event) {
console.log(event.type);
}
// benannte Funktion als dritten Event-Listener für 'blur' event registrieren
inputField.addEventListener("blur", onBlurHandler);
// de-registrieren von drittem Event-Listener
// GEHT NUR BEI BENANNTEN FUNKTIONEN!
inputField.removeEventListener("blur", onBlurHandler);
// benannte Funktion mit Argument als Event-Listener für 'blur' registrieren
inputField.addEventListener("blur", (event) => onBlurHandlerWithArgs(event));
// de-registriere Funktion mit Argumenten als Event-Listener
inputField.removeEventListener("blur", onBlurHandlerWithArgs);
Note
The blur event is fired, when the element has lost focus (e.g. user selects
a different element).
DOMContentLoaded Event Listener
Feuert etwas früher, wie load event. Siehe Dokumentation.
Event-Handler
Ist eine Eigenschaft eines Elements, welche direkt einer Funktion zugewiesen werden kann. Diese wird ebenfalls beim Eintreten des Ereignisses ausgeführt. Es gibt hier nur eine Funktion. Sobald die Eigenschaft einer anderen Funktion zugewiesen wird, wird die bisherige Zuweisung überschrieben.
Note
Jeder Event-Handler beginnt mit on plus dem Event Namen und kann in den
Elementen nachgeschaut werden. Event-Handler Properties sind alle im lower-Case,
ohne Trennzeichen, geschrieben (z.B. onclick).
let inputField = document.querySelector("#eingabe");
function log_msg (msg) {
console.log(msg);
}
// Event-Handler für 'blur' event zuweisen
inputField.onblur = function() {
log_msg("Ich handle das allein");
};
// Event-Handler für 'blur' event wird überschrieben
inputField.onblur = function() {
log_msg("Denkste! Ich bin jetzt der Chef! Hinfort!");
};
function gibMeldungDazu() {
console.log("Das Event ist wirklich eingetreten");
}
// Ausgelagerte Funktion als Event-Handler registrieren (OHNE Klammern übergeben)
inputField.onfocus = gibMeldungDazu;
console.log(gibMeldungDazu); // function
console.log(gibMeldungDazu()); // undefined (da kein Return-value)
// Event-Handler de-registrieren (auf 'null' setzen)
inputField.onfocus = null;
DOM-Event-Handler
Der onload Event-Handler wird erst nach abgeschlossenen Laden des DOMs und allen
verknüpften Resources (CSS, Bilder, usw.) aufgerufen.
let inputField = document.querySelector("#eingabe");
// DOM-Event-Handler
window.onload = function() {
console.log("DOM und alle verknüpften Ressourcen fertig geladen");
};