Übung: HTL Sportfest – Die Seite baut sich selbst

Ziel: Du erzeugst neue Elemente per JavaScript, hängst sie an die richtige Stelle im DOM-Baum, entfernst und ersetzt Knoten – und baust aus einem Datenarray eine komplette Tabelle und Newsliste.

Voraussetzung: Exercise-JS-CSS-Selektoren-Sportfest – dort hast du Vorhandenes gefunden und verändert. Jetzt steht in der HTML-Datei fast nichts mehr drin: Die Inhalte kommen aus den Daten.

Unterlagen:

Der Dreischritt Jedes neue Element durchläuft immer dieselben drei Schritte:

  1. erzeugen – document.createElement("…")
  2. füllen – Text, Klassen, Attribute setzen
  3. einhängen – append, prepend, before, after

Solange Schritt 3 fehlt, existiert das Element nur im Speicher und ist unsichtbar.


Setup

Lege einen neuen Ordner sportfest-2/ mit vier Dateien an: index.html, style.css, daten.js, script.js. Öffne index.html im Browser und die Entwicklerkonsole (F12 bzw. ⌥⌘I). Lass auch das Elements-Panel offen – dort siehst du live, was im Baum passiert.

index.html (nicht verändern!)

<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <title>HTL Sportfest 2026</title>
  <link rel="stylesheet" href="style.css">
  <script src="daten.js" defer></script>
  <script src="script.js" defer></script>
</head>
<body>
  <header id="top">
    <h1>HTL Sportfest 2026</h1>
    <p class="subtitle">Medaillenspiegel der Klassen</p>
  </header>
 
  <main>
    <section id="tabelle">
      <h2>Medaillenspiegel</h2>
      <table id="medals">
        <thead>
          <tr><th>Klasse</th><th>🥇</th><th>🥈</th><th>🥉</th></tr>
        </thead>
        <tbody id="medal-body">
          <!-- wird per JavaScript befüllt -->
        </tbody>
      </table>
    </section>
 
    <section id="news">
      <h2>News</h2>
      <div id="news-list">
        <!-- wird per JavaScript befüllt -->
      </div>
    </section>
 
    <section id="disziplinen">
      <h2>Disziplinen</h2>
      <p id="platzhalter" class="hint">Wird geladen …</p>
    </section>
  </main>
 
  <footer>
    <p>HTL Krems – Sportfest 2026</p>
  </footer>
</body>
</html>

Reihenfolge der Skripte Beide Skripte haben defer und werden daher in der angegebenen Reihenfolge ausgeführt, nachdem das HTML geladen ist. Deshalb kennt script.js bereits die Variablen aus daten.js.

style.css

* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 1rem; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ccc; padding: .4rem .8rem; text-align: center; }
.team-name { text-align: left; font-weight: bold; }
 
/* Hilfsklassen für JavaScript */
.leader { background: #ffe58a; }
.new    { border-left: 4px solid #2a9d8f; padding-left: .5rem; }
.hint   { color: #666; font-style: italic; }
.ticker { background: #e63946; color: white; padding: .4rem .8rem; border-radius: 4px; }
.nach-oben { display: inline-block; margin-top: .5rem; font-size: .9rem; }

daten.js (nicht verändern!)

const teams = [
  { klasse: "1AHIT", gold: 2, silber: 1, bronze: 4 },
  { klasse: "2BHIT", gold: 3, silber: 0, bronze: 1 },
  { klasse: "3AHIT", gold: 3, silber: 2, bronze: 0 },
  { klasse: "4CHIT", gold: 0, silber: 7, bronze: 5 },
  { klasse: "5AHIT", gold: 1, silber: 3, bronze: 3 }
];
 
// chronologisch: ältester Eintrag zuerst
const news = [
  { titel: "Siegerehrung",           text: "Die Siegerehrung beginnt um 16:30 in der Aula.",        neu: false },
  { titel: "Staffellauf verschoben", text: "Der Staffellauf findet wegen Regen erst um 14:00 statt.", neu: false },
  { titel: "Neuer Schulrekord",      text: "Im Weitsprung wurde ein neuer Schulrekord aufgestellt.", neu: true  }
];
 
const disziplinen = ["Staffellauf", "Weitsprung", "Völkerball", "Tauziehen"];

script.js (Startgerüst)

// ===== Teil B – Erzeugen, füllen, einhängen =====
 
 
// ===== Teil C – Aus Daten wird Seite =====
 
 
// ===== Teil D – Entfernen & Ersetzen =====
 
 
// ===== Teil E – createElement vs. innerHTML =====
 
// Gegeben für E2 – simuliert eine eingesendete Leser-News:
const leserNews = {
  titel: 'Tolles Fest! <img src="x" onerror="alert(\'Gehackt!\')">',
  text: "Von einem Besucher eingesendet.",
  neu: true
};

Teil A – Wo landet dieses Element? (auf Papier) 📄

Ausgangslage ist diese Liste:

<ul id="liste">
  <li id="a">A</li>
  <li id="b">B</li>
</ul>

Die folgenden Schritte werden nacheinander ausgeführt. Zu Beginn gilt:

const liste = document.querySelector("#liste");
const a = document.querySelector("#a");
const b = document.querySelector("#b");

A1. Trage nach jedem Schritt ein, welche Einträge die Liste von oben nach unten enthält.

SchrittCodeInhalt der Liste
0(Ausgangslage)A, B
1const x = document.createElement("li"); x.textContent = "X";
2liste.append(x);
3const y = document.createElement("li"); y.textContent = "Y"; liste.prepend(y);
4const z = document.createElement("li"); z.textContent = "Z"; b.before(z);
5liste.append(a);
6x.remove();
7const w = document.createElement("li"); w.textContent = "W"; b.replaceWith(w);

A2. Bei Schritt 5 wird kein neues Element erzeugt. Was passiert mit a? Formuliere eine Regel in einem Satz.

A3. Prüfe deine Vorhersagen: Lege eine kleine Datei teil-a.html mit der Liste an und führe die Schritte in der Konsole aus.


Teil B – Erzeugen, füllen, einhängen 🧱

B1. Erzeuge ein <p> mit der Klasse ticker und dem Text 🔴 Live-Ticker gestartet. Häng es noch nicht ein!

  1. Gib das Element mit console.log() aus. Ist es auf der Seite zu sehen?
  2. Gib element.parentNode aus. Was erhältst du?
  3. Erkläre als Kommentar, warum der Absatz unsichtbar ist.

B2. Häng den Ticker jetzt so ein, dass er das erste Kind von <main> wird. Welche Methode wählst du?

B3. Erzeuge einen Absatz <p class="hint">Alle Angaben ohne Gewähr.</p> und füge ihn direkt nach der Tabelle #medals ein – also neben der Tabelle, nicht in ihr. Kontrolliere im Elements-Panel.

B4. 🔗 Jede <section> soll am Ende einen Link ↑ nach oben bekommen (href="#top", Klasse nach-oben).

  1. Erzeuge den Link einmal vor der Schleife und hänge ihn in der Schleife an jede Section an. Wie viele Links siehst du – und wo?
  2. Verschiebe das Erzeugen in die Schleife. Was ändert sich?
  3. Erkläre den Unterschied als Kommentar. (Tipp: Teil A, Schritt 5)

B5. append vs. appendChild: Probiere in der Konsole beide Zeilen aus und notiere als Kommentar, was passiert.

document.querySelector("footer").append(" – Stand: heute", document.createElement("hr"));
document.querySelector("footer").appendChild(" – Stand: heute");

Teil C – Aus Daten wird Seite 📊

Darstellung ist eine Funktion der Daten Die Tabelle steht nicht mehr im HTML. Sie entsteht aus dem Array teams: Für jedes Objekt wird ein Element erzeugt und in einen Container gehängt.

C1. Schreib eine Funktion zeileErstellen(team), die ein fertiges <tr> zurückgibt (aber nicht einhängt):

  • Klasse team und data-team mit dem Klassennamen
  • vier <td>-Zellen mit den Klassen team-name, gold, silver, bronze

Teste sie mit console.log(zeileErstellen(teams[0])).

C2. Schreib eine Funktion renderTabelle(liste), die für jedes Team mit zeileErstellen eine Zeile erzeugt und an #medal-body anhängt. Rufe renderTabelle(teams) auf – die Tabelle soll aussehen wie in der letzten Übung.

C3. Schreib analog newsErstellen(eintrag) und renderNews(liste):

  • <article class="news-item"> mit <h3> (Titel) und <p> (Text)
  • Ist eintrag.neu wahr, bekommt der Artikel zusätzlich die Klasse new
  • Alle Artikel landen in #news-list

C4. Das Array news ist chronologisch sortiert (ältester Eintrag zuerst). Auf der Seite soll aber die neueste News oben stehen. Löse das, ohne das Array zu verändern – nur durch die Wahl der Einfügemethode.


Teil D – Entfernen & Ersetzen ✂️

D1. Ersetze den Platzhalter #platzhalter durch eine <ul>, die für jede Disziplin aus disziplinen ein <li> enthält. Verwende replaceWith().

D2. Die Siegerehrung wurde abgesagt. Suche den News-Artikel, dessen Überschrift Siegerehrung lautet, und entferne ihn mit remove(). → Lade die Seite neu. Warum ist der Artikel wieder da? Was müsstest du stattdessen ändern? (Kommentar)

D3. 🥇 Eilmeldung: Die 4CHIT gewinnt Gold im Tauziehen!

  1. Erhöhe im Datenobjekt der 4CHIT den Wert gold um 1. Tipp: teams.find(t => t.klasse === "4CHIT")
  2. Rufe renderTabelle(teams) noch einmal auf. Was siehst du in der Tabelle?
  3. Behebe das Problem in der Funktion renderTabelle, sodass sie beliebig oft aufgerufen werden kann. Tipp: replaceChildren()

D4. Sortiere teams nach den Regeln des Medaillenspiegels (Gold, bei Gleichstand Silber, dann Bronze) und rendere die Tabelle neu. Die erste Zeile bekommt die Klasse leader.

teams.sort((a, b) => b.gold - a.gold || b.silber - a.silber || b.bronze - a.bronze);

→ Wer führt jetzt? Beachte: Du hast nur die Daten geändert und dann neu gezeichnet – keine einzige Zelle von Hand.


Teil E – createElement vs. innerHTML ⚖️

E1. Schreib eine zweite Funktion renderNewsTemplate(liste), die dieselben News-Artikel mit Template-Literal und innerHTML erzeugt:

`<article class="news-item">
   <h3>${eintrag.titel}</h3>
   <p>${eintrag.text}</p>
 </article>`

Vergleiche: Wie viele Zeilen brauchst du hier, wie viele in C3?

E2. 🔒 Füge leserNews (siehe Startgerüst) zum Array news hinzu und rendere die News

  1. mit renderNewsTemplate → Was passiert?
  2. mit renderNews aus C3 → Was passiert jetzt?

(Erinnerung: Teil C4 der letzten Übung.)

E3. Beantworte als Kommentar in 2–3 Sätzen: Wann ist die Template-Literal-Variante völlig in Ordnung, und wann wird sie gefährlich?


Bonus ⭐

X1. 🚀 Erzeuge in einer Schleife 500 Test-Teams (T001, T002, …) und rendere sie

  1. mit renderTabelle (500 einzelne append-Aufrufe in den sichtbaren Baum),
  2. mit einem DocumentFragment: alle Zeilen zuerst ins Fragment, am Ende ein einziges append in #medal-body.

Miss beide Varianten mit console.time("render") / console.timeEnd("render"). Erkläre als Kommentar, warum die zweite Variante dem Browser Arbeit erspart.

X2. Kopiere die erste Tabellenzeile mit cloneNode(true), ändere in der Kopie den Klassennamen auf GAST und hänge sie an. Probiere danach cloneNode(false) – was fehlt?

X3. Für diese Aufgabe darfst du index.html ergänzen: Lege ein <template id="news-vorlage"> mit dem Grundgerüst eines News-Artikels an. Baue newsErstellen so um, dass sie die Vorlage klont und nur noch Titel und Text einsetzt.


Abgabe

  • script.js mit allen Lösungen, jeweils mit Kommentar // B1, // B2, …
  • Lösungstabelle zu Teil A (Foto oder als Kommentar am Anfang von script.js)
  • Antworten auf die Fragen aus B1, B4, B5, D2, E2 und E3 als Kommentare im Code