# Interaktives Frontend mit reinem JavaScript

## Inhaltsverzeichnis

- [Das DOM ohne Framework ansprechen](#das-dom-ohne-framework-ansprechen)
- [Auf Ereignisse reagieren](#auf-ereignisse-reagieren)
- [Eine Liste live filtern](#eine-liste-live-filtern)
- [Daten im Browser behalten: localStorage](#daten-im-browser-behalten-localstorage)
- [Eine Aufgabe automatisch einordnen](#eine-aufgabe-automatisch-einordnen)
- [Formulareingaben prüfen](#formulareingaben-prüfen)
- [jQuery: dasselbe kürzer](#jquery-dasselbe-kürzer)
- [Beispielaufgabe](#beispielaufgabe)
- [Zusammenfassung / Spickzettel](#zusammenfassung--spickzettel)

---

## Das DOM ohne Framework ansprechen

Ohne React, Vue oder Ähnliches heisst Interaktivität: ein Element im HTML suchen, seinen Inhalt oder seine Klasse ändern, fertig. Die drei Werkzeuge, mit denen fast alles geht:

```javascript
const feld = document.getElementById('titel');
const alle = document.querySelectorAll('.aufgabe');

feld.textContent = 'Neuer Titel';       // Text ersetzen
feld.classList.add('erledigt');         // Klasse setzen
feld.classList.toggle('versteckt');     // Klasse an/aus schalten
```

Der Unterschied zwischen `textContent` und `innerHTML`: `textContent` behandelt alles als reinen Text, `innerHTML` würde darin enthaltene Tags tatsächlich als HTML einbauen. Kommt der Text von einer Eingabe des Nutzers, gehört dort fast immer `textContent` hin, sonst könnte jemand über das Eingabefeld eigenes HTML einschleusen.

## Auf Ereignisse reagieren

Jede Interaktion ist ein Ereignis, auf das man mit `addEventListener` hört:

```javascript
document.getElementById('speichern').addEventListener('click', () => {
  // wird bei jedem Klick ausgeführt
});

document.getElementById('suchfeld').addEventListener('input', (ereignis) => {
  const text = ereignis.target.value;
  // wird bei jedem Tastendruck im Feld ausgeführt
});
```

Für ein Suchfeld ist `input` das passendere Ereignis als `keyup`: `input` löst auch bei Einfügen mit der Maus oder Diktat aus, `keyup` nur bei tatsächlich gedrückten Tasten.

## Eine Liste live filtern

Eine Live-Suche ist kein eigenes Bauteil, sondern nur: bei jeder Eingabe die vorhandene Liste neu durchgehen und entscheiden, was sichtbar bleibt.

```javascript
function filtereListe() {
  const gesuchtes = suchfeld.value.trim().toLowerCase();
  const eintraege = document.querySelectorAll('.aufgabe');

  eintraege.forEach((eintrag) => {
    const passtDazu = eintrag.dataset.titel.toLowerCase().includes(gesuchtes);
    eintrag.hidden = !passtDazu;
  });
}
```

`eintrag.hidden = true` statt `eintrag.style.display = 'none'` zu setzen, hat einen Nebeneffekt, den man leicht übersieht: `hidden` nimmt das Element auch aus der Tab-Reihenfolge, `display: none` über eine eigene Klasse müsste man das nicht zwingend.

## Daten im Browser behalten: localStorage

Ohne eigenen Server müssen Daten trotzdem einen Seitenaufruf überleben. `localStorage` speichert Text im Browser, über Neuladen und Neustart hinweg. Da er nur Strings kann, müssen Objekte vorher in einen String verwandelt werden:

```javascript
function ladeAufgaben() {
  const gespeichert = localStorage.getItem('aufgaben');
  if (!gespeichert) return [];
  try {
    return JSON.parse(gespeichert);
  } catch (fehler) {
    console.error('Gespeicherte Daten waren beschädigt:', fehler);
    return [];
  }
}

function speichereAufgaben(liste) {
  localStorage.setItem('aufgaben', JSON.stringify(liste));
}
```

Das `try/catch` beim Laden ist kein Vorsichtsmassnahme-Theater: Wenn jemand die Browser-Konsole öffnet und den gespeicherten Wert von Hand verändert (oder ein alter Datensatz aus einer früheren Version nicht mehr zum aktuellen Format passt), würde `JSON.parse()` sonst die ganze Seite mit einem Fehler abbrechen, statt einfach mit einer leeren Liste weiterzumachen.

## Eine Aufgabe automatisch einordnen

Für die Eisenhower-Matrix (wichtig/dringend als zwei unabhängige Ja/Nein-Fragen) reicht eine kleine Funktion, die aus zwei Wahrheitswerten eine von vier Kategorien macht:

```javascript
function ordneEin(istWichtig, istDringend) {
  if (istWichtig && istDringend)  return 'sofort erledigen';
  if (istWichtig && !istDringend) return 'einplanen';
  if (!istWichtig && istDringend) return 'delegieren';
  return 'streichen';
}
```

Die Reihenfolge der Bedingungen ist dabei wichtig: `istWichtig && istDringend` muss vor den beiden einfacheren Fällen geprüft werden, sonst würde die erste zutreffende, aber zu grobe Bedingung (nur `istWichtig`) bereits greifen.

## Formulareingaben prüfen

Bevor eine Eingabe gespeichert wird, prüfe ich sie im Browser, damit ein Fehler sofort sichtbar ist und nicht erst nach dem Absenden:

```javascript
function pruefeTitel(wert) {
  const bereinigt = wert.trim();
  if (bereinigt === '') return 'Titel darf nicht leer sein.';
  if (bereinigt.length > 120) return 'Titel ist zu lang (maximal 120 Zeichen).';
  return '';   // leerer String heisst: kein Fehler
}
```

Diese Prüfung ersetzt keine serverseitige Kontrolle (die es hier mangels Server auch nicht braucht), sie ist reiner Komfort: eine sofortige Rückmeldung, bevor überhaupt etwas gespeichert wird.

## jQuery: dasselbe kürzer

jQuery war zum Zeitpunkt dieses Moduls noch Teil des Lehrplans, auch wenn modernes JavaScript vieles davon inzwischen selbst kann. Dieselbe Suchfeld-Logik in jQuery:

```javascript
$('#suchfeld').on('input', function () {
  const gesuchtes = $(this).val().toLowerCase();
  $('.aufgabe').each(function () {
    const passtDazu = $(this).data('titel').toLowerCase().includes(gesuchtes);
    $(this).toggle(passtDazu);
  });
});
```

`$(...)` ersetzt `document.querySelector(...)`, `.on()` ersetzt `addEventListener()`, `.toggle()` blendet ein Element ein oder aus. Für ein einzelnes Suchfeld ist der Unterschied klein, bei mehreren verketteten Operationen auf derselben Auswahl wird jQuery deutlich kürzer.

## Beispielaufgabe

Eine Übung aus dem Modul: eine Liste von Einträgen nach mehreren Kriterien gleichzeitig filtern (Text und Kategorie), ohne dass sich die beiden Filter gegenseitig überschreiben. Der Trick dabei: nicht bei jedem Filter neu vom Original ausgehen, sondern eine Funktion schreiben, die alle aktiven Bedingungen auf einmal gegen jeden Eintrag prüft.

## Zusammenfassung / Spickzettel

| Thema | Kernaussage |
|---|---|
| Element auswählen | `querySelector` / `querySelectorAll`, dann Eigenschaft direkt setzen |
| Auf Eingaben reagieren | `addEventListener('input', …)`, nicht zwingend `keyup` |
| Liste filtern | Bei jeder Eingabe neu durchgehen, `hidden` statt `display` |
| Daten behalten ohne Server | `localStorage` mit `JSON.stringify` / `JSON.parse`, mit `try/catch` |
| Aufgaben einordnen | Zwei Ja/Nein-Werte, eine Funktion mit vier Ausgängen |
| Kurzform | jQuery: `$()`, `.on()`, `.toggle()` statt der DOM-Methoden einzeln |
