Zum Inhalt springen
aviral gupta

// B6.1 · ca. 30 Min. · Einstieg

Elemente auswählen und ändern

Nach dieser Lektion finden Sie Elemente auf einer Seite, ändern ihren Text sicher und setzen Attribute, data-Werte und Klassen aus JavaScript.

Lektion 1 von 5 in B6 Das DOM und Ereignisse

Anfang des Moduls

Danach können Sie

  • Elemente mit querySelector, querySelectorAll und getElementById auswählen, eine NodeList in ein Array umwandeln und mit null rechnen, wenn nichts passt
  • Ändern, was ein Element zeigt, mit textContent, und erklären, warum textContent und nicht innerHTML die sichere Wahl für Text von Nutzern ist
  • Attribute, data-*-Werte und Klassen mit getAttribute, setAttribute, dataset und classList lesen und ändern
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen aus Lektion B5.3: Was gibt dieser Code aus?

    const list = ["tea", "milk"];
    const more = [...list, "eggs"];
    console.log(list.length, more.length);
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie weiterlesen. Die Seite hat <p id="msg">Hi</p>. Was gibt der Code aus: wie viele Elemente im Absatz stehen und welcher Text?

    const msg = document.querySelector('#msg');
    msg.textContent = '<b>Bye</b>';
    console.log(msg.children.length, msg.textContent);
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie die Methode, die das erste Element findet, auf das der CSS-Selektor #title passt.

    const title = document.____('#title');
    console.log(title.textContent);
    const title = document.('#title');
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Aufruf zu, was er zurückgibt.

  5. Üben · Aufgabe 5 von 7

    Die Liste ist <ul id="list"><li class="tag">html</li>…</ul>. Was gibt dieser Code aus?

    const li = document.querySelector('#list li');
    li.classList.add('on');
    li.classList.remove('tag');
    console.log(li.classList.contains('tag'), li.className);
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Die Liste hat zu Beginn zwei Einträge. Was gibt die letzte Zeile aus?

    const items = document.querySelectorAll('#list li');
    const list = document.querySelector('#list');
    list.append(document.createElement('li'));
    console.log(items.length, list.children.length);
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe: eine Todo-Überschrift, die mitzählt. Schreiben Sie eine index.html mit einer Überschrift <h1 id="heading"> und einer Liste von drei Todos, jedes mit data-done="yes" oder "no". Wählen Sie in main.js die Einträge aus und verteilen Sie sie mit Spread in ein Array, geben Sie den erledigten die Klasse done und setzen Sie die Überschrift mit textContent auf "Todo: 2 of 3 done". Geben Sie die Namen der Todos aus, und öffnen Sie die Seite über einen lokalen Webserver, um sie zu sehen.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Leseliste, die sich selbst aktualisiert

Die Seite hat eine Überschrift, eine Liste mit drei Büchern und ihren Seitenzahlen in data-pages sowie einen Hinweis. main.js wählt die Einträge aus, macht aus der NodeList ein Array, um Namen und Zahlen zu lesen, aktualisiert die Überschrift, markiert lange Bücher mit einer Klasse und setzt zwei Attribute. Dann schreibt es denselben String einmal mit textContent und einmal mit innerHTML in den Hinweis, damit Sie den Unterschied sehen, und fragt nach einem Element, das es nicht gibt.

main.js

// main.js runs after the HTML is parsed: module scripts are deferred.
const title = document.querySelector('#title');
const items = document.querySelectorAll('#books li');
console.log(items.length, typeof items.map);

// A NodeList has no map, so spread it into an array first.
const names = [...items].map((li) => li.textContent);
console.log(names);
const pages = [...items].map((li) => Number(li.dataset.pages));
console.log(pages);

title.textContent = 'My books (' + items.length + ')';
console.log(title.textContent);

for (const li of items) {
  if (Number(li.dataset.pages) > 400) li.classList.add('long');
}
items[0].dataset.read = 'yes';
items[2].setAttribute('title', 'A short one');
console.log(items[0].getAttribute('data-read'), items[1].classList.contains('long'));

// textContent shows text as it is; innerHTML reads it as HTML.
const note = document.getElementById('note');
note.textContent = '<b>2</b> long books';
console.log(note.textContent, note.children.length);
note.innerHTML = '<b>2</b> long books';
console.log(note.textContent, note.children.length);
console.log(note.classList.toggle('hint'), note.classList.length);
console.log(document.querySelector('#missing'));

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Reading list</title>
</head>
<body>
  <h1 id="title">My books</h1>
  <ul id="books"><li data-pages="412">Dune</li><li data-pages="474">Emma</li><li data-pages="96">Animal Farm</li></ul>
  <p id="note" class="hint">Loading...</p>
  <script type="module" src="main.js"></script>
</body>
</html>

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Ausgabe

3 undefined
[ 'Dune', 'Emma', 'Animal Farm' ]
[ 412, 474, 96 ]
My books (3)
yes true
<b>2</b> long books 0
2 long books 1
false 0
null
<!-- #books -->
<ul id="books"><li data-pages="412" class="long" data-read="yes">Dune</li><li data-pages="474" class="long">Emma</li><li data-pages="96" title="A short one">Animal Farm</li></ul>
  • typeof items.map ist undefined: Eine NodeList hat kein map, deshalb verteilt das Programm sie zuerst mit Spread.
  • dataset.pages ist der String "412"; Number() macht daraus eine Zahl für den Vergleich.
  • Mit textContent hat der Hinweis keine Kindelemente; mit innerHTML wurde aus <b> eines.
  • toggle hat hint entfernt und false zurückgegeben, der Hinweis hat also keine Klassen mehr.
  • querySelector gab für #missing null aus: Ein Fehler entsteht erst, wenn Sie null benutzen.
  • Der Snapshot zeigt die Klassen und Attribute, die das Skript der Liste hinzugefügt hat.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Übungen

Übung 1 von 2

Erledigte Aufgaben abhaken

Die Seite listet vier Aufgaben, jede mit data-done="yes" oder data-done="no". main.js soll jeder erledigten Aufgabe die Klasse done geben, und keiner anderen, und den Absatz #summary auf "2 of 4 done" setzen, gezählt auf der Seite. Der Starter markiert nur den ersten Eintrag und schreibt eine feste Zusammenfassung. Wählen Sie alle Einträge aus, verteilen Sie sie in ein Array und verwenden Sie dataset und classList.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Nur die erste Aufgabe ist markiert, und die Zusammenfassung ist fester Text.

  2. Hinweis 2

    [...document.querySelectorAll('#tasks li')] ergibt ein Array, das Sie durchlaufen und filtern können.

  3. Hinweis 3

    task.dataset.done ist der String 'yes' oder 'no'; zählen Sie mit filter(...).length und bauen Sie den Text aus dieser Zahl und tasks.length.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

const tasks = [...document.querySelectorAll('#tasks li')];
for (const task of tasks) {
  if (task.dataset.done === 'yes') task.classList.add('done');
}

const done = tasks.filter((task) => task.dataset.done === 'yes').length;
document.querySelector('#summary').textContent = done + ' of ' + tasks.length + ' done';
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

const task = document.querySelector('#tasks li');
task.classList.add('done');

// TODO: mark every finished task, then count them for the summary.
document.querySelector('#summary').textContent = '1 of 1 done';

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadPage} from './learnrun.js';

const marked = (page) => [...page.document.querySelectorAll('#tasks li')].map((li) => li.classList.contains('done'));

test('Jede erledigte Aufgabe hat die Klasse done', async () => {
  const page = await loadPage();
  const got = marked(page);
  assert.equal(got[0] && got[2], true, 'die Klasse done steht an diesen Aufgaben: ' + JSON.stringify(got) + ', erwartet an der 1. und 3.');
});

test('Offene Aufgaben bekommen die Klasse done nicht', async () => {
  const page = await loadPage();
  const got = marked(page);
  assert.deepEqual(got, [true, false, true, false], 'die Klasse done steht an diesen Aufgaben: ' + JSON.stringify(got));
});

test('Die Zusammenfassung lautet 2 of 4 done', async () => {
  const page = await loadPage();
  const text = page.document.querySelector('#summary').textContent;
  assert.equal(text, '2 of 4 done', 'die Zusammenfassung lautet ' + JSON.stringify(text));
});

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Today</title>
</head>
<body>
  <h1>Today</h1>
  <ul id="tasks">
    <li data-done="yes">Buy milk</li>
    <li data-done="no">Call Ada</li>
    <li data-done="yes">Water the plants</li>
    <li data-done="no">Book tickets</li>
  </ul>
  <p id="summary"></p>
  <script type="module" src="main.js"></script>
</body>
</html>

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npm install jsdom@28.1.0
node --test
learnrun.js herunterladen

Übung 2 von 2

Eine Profilkarte, die Text als Text zeigt

Das Profil stammt aus einem Anmeldeformular, jeder Wert ist also Text, den ein Nutzer eingegeben hat, und die Bio enthält Markup. Füllen Sie die Karte: #name und #bio zeigen die Werte genau, als Text, ohne daraus gebaute Elemente; das Bild #photo bekommt src mit dem Foto und alt mit "Photo of " plus dem Namen; die Karte bekommt data-role mit der Rolle und die Klasse admin, wenn die Rolle "admin" ist, und behält ihre Klasse card. Der Starter verwendet innerHTML und lässt den Rest offen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Ihre Seite

Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.

Führen Sie das Programm aus, um seine Seite hier zu sehen.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Die Bio zeigt "I love maths" fett, weil innerHTML aus dem Text ein <b>-Element gebaut hat.

  2. Hinweis 2

    Ersetzen Sie innerHTML durch textContent und verwenden Sie dann photo.setAttribute('src', …) und photo.setAttribute('alt', …).

  3. Hinweis 3

    card.dataset.role = profile.role schreibt data-role; classList.add('admin') fügt eine Klasse hinzu und behält card.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// Values a user typed into a sign-up form: treat them as text.
const profile = { name: 'Ada Lovelace', bio: 'I love <b>maths</b>', role: 'admin', photo: 'ada.png' };

const card = document.querySelector('#card');
document.querySelector('#name').textContent = profile.name;
document.querySelector('#bio').textContent = profile.bio;

const photo = document.querySelector('#photo');
photo.setAttribute('src', profile.photo);
photo.setAttribute('alt', 'Photo of ' + profile.name);

card.dataset.role = profile.role;
if (profile.role === 'admin') card.classList.add('admin');
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// Values a user typed into a sign-up form: treat them as text.
const profile = { name: 'Ada Lovelace', bio: 'I love <b>maths</b>', role: 'admin', photo: 'ada.png' };

const card = document.querySelector('#card');
document.querySelector('#name').innerHTML = profile.name;
document.querySelector('#bio').innerHTML = profile.bio;
// TODO: the photo and its alt text, data-role, and the class admin.

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {loadPage} from './learnrun.js';

test('Die Bio erscheint als Text, nicht als HTML', async () => {
  const page = await loadPage();
  const bio = page.document.querySelector('#bio');
  assert.equal(bio.children.length, 0, 'die Bio enthält ' + bio.children.length + ' aus dem Text gebaute(s) Element(e), erwartet keines');
  assert.equal(bio.textContent, 'I love <b>maths</b>', 'die Bio zeigt ' + JSON.stringify(bio.textContent));
});

test('Der Name wird angezeigt', async () => {
  const page = await loadPage();
  const text = page.document.querySelector('#name').textContent;
  assert.equal(text, 'Ada Lovelace', 'der Name zeigt ' + JSON.stringify(text));
});

test('Das Foto hat src und Alternativtext', async () => {
  const page = await loadPage();
  const photo = page.document.querySelector('#photo');
  assert.equal(photo.getAttribute('src'), 'ada.png', 'src ist ' + JSON.stringify(photo.getAttribute('src')));
  assert.equal(photo.getAttribute('alt'), 'Photo of Ada Lovelace', 'alt ist ' + JSON.stringify(photo.getAttribute('alt')));
});

test('Die Karte hat data-role und die Klassen card und admin', async () => {
  const page = await loadPage();
  const card = page.document.querySelector('#card');
  assert.equal(card.getAttribute('data-role'), 'admin', 'data-role ist ' + JSON.stringify(card.getAttribute('data-role')));
  assert.equal(card.classList.contains('admin'), true, 'die Karte hat keine Klasse admin');
  assert.equal(card.classList.contains('card'), true, 'die Karte hat ihre Klasse card verloren');
});

index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Profile</title>
</head>
<body>
  <article id="card" class="card">
    <img id="photo" src="blank.png" alt="">
    <h2 id="name">Name</h2>
    <p id="bio"></p>
  </article>
  <script type="module" src="main.js"></script>
</body>
</html>

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

Um die Seite auf Ihrem Computer auszuführen, öffnen Sie index.html über einen lokalen Webserver, nicht als Datei: Browser führen Modul-Skripte von file://-Adressen nicht aus. Für die Prüfungen installieren Sie zuerst jsdom (die Befehle unten).

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

npm install jsdom@28.1.0
node --test
learnrun.js herunterladen

Häufige Fehler

Ein Selektor, auf den nichts passt

const title = document.querySelector('#titel');
title.textContent = 'My tags';

Was Node.js ausgibt

TypeError: Cannot set properties of null (setting 'textContent')

Warum, und die Lösung

Die id ist #titel geschrieben, und kein Element hat sie, also hat querySelector null zurückgegeben. Der Fehler kommt eine Zeile später, wenn das Programm eine Eigenschaft von null setzt. Vergleichen Sie den Selektor Zeichen für Zeichen mit index.html: Hier muss es #title heißen. Wenn ein Element wirklich fehlen kann, prüfen Sie zuerst: if (title) { … }. Firefox und Safari formulieren diese Meldung anders.

map auf einer NodeList aufrufen

const tags = document.querySelectorAll('.tag');
const names = tags.map((li) => li.textContent);
console.log(names);

Was Node.js ausgibt

TypeError: tags.map is not a function

Warum, und die Lösung

querySelectorAll gibt eine NodeList zurück, die wie ein Array aussieht, aber keines ist: Sie hat length, Indizes und forEach, aber kein map, filter oder reduce. Verteilen Sie sie zuerst in ein echtes Array: [...tags].map((li) => li.textContent). Eine for...of-Schleife über die NodeList funktioniert ebenfalls.

Die NodeList wie ein einzelnes Element behandeln

const tags = document.querySelectorAll('.tag');
tags.classList.add('on');

Was Node.js ausgibt

TypeError: Cannot read properties of undefined (reading 'add')

Warum, und die Lösung

classList gehört zu jedem einzelnen Element, nicht zur Liste, also ist tags.classList undefined, und add darauf aufzurufen scheitert. Ändern Sie jedes Element in einer Schleife: for (const tag of tags) tag.classList.add('on'). Wenn Sie nur das erste meinen, nehmen Sie querySelector, das ein einzelnes Element zurückgibt.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Die Seite ist ein Baum, und Selektoren finden seine Knoten

Der Browser macht aus dem HTML das DOM, einen Baum aus Knoten: oben document, darunter html, head und body, und jedes Element mit Eltern und Kindern. document.querySelector(selector) nimmt einen CSS-Selektor und gibt das erste passende Element zurück, oder null. querySelectorAll gibt eine NodeList aller Treffer zurück, leer, wenn keiner passt, und sie wächst nicht mit, wenn später Elemente dazukommen. Eine NodeList hat length und forEach, aber kein map oder filter: [...list] macht ein Array daraus. getElementById("title") nimmt die id ohne #. main.js wird als Modul-Skript geladen, und Module sind deferred: Das ganze HTML ist geparst, bevor es läuft, ein DOMContentLoaded-Listener ist nicht nötig.

textContent für Text, innerHTML nur für eigenes Markup

el.textContent liest den ganzen Text in einem Element; beim Setzen ersetzt es alles darin durch ein Stück Text, genau so angezeigt, wie es geschrieben ist, "<b>Hi</b>" erscheint also als diese Zeichen. el.innerHTML liest den String als HTML und baut Elemente daraus. Für Markup, das Sie selbst geschrieben haben, ist das in Ordnung, aber Text, den ein Nutzer eingibt, kann Markup enthalten, und innerHTML würde es in Ihre Seite einbauen; so funktionieren XSS-Angriffe, die Modul A5 behandelt. Text von Nutzern, aus Formularen oder von anderen Seiten kommt deshalb immer mit textContent hinein. Browser kennen auch innerText, das vom CSS-Layout abhängt; dieser Kurs nutzt textContent.

Attribute, data-*-Werte und Klassen

getAttribute("alt") gibt den Wert eines Attributs als String zurück, oder null, wenn das Element es nicht hat. setAttribute("alt", "A fox") ändert es oder fügt es hinzu. Attribute, die mit data- beginnen, tragen Ihre eigenen Daten: data-pages="412" ist el.dataset.pages, und aus data-due-date wird el.dataset.dueDate. Werte in dataset sind immer Strings, wandeln Sie sie also mit Number() um, bevor Sie rechnen. classList ändert eine Klasse und lässt die anderen stehen: add, remove, contains und toggle, das eine fehlende Klasse hinzufügt und eine vorhandene entfernt und true zurückgibt, wenn die Klasse danach da ist. Gestaltung über Klassen hält das Aussehen im CSS und die Logik im JavaScript.

Quellen

Zuletzt geprüft am 4. Oktober 2026