mitmario.dev

Synthese: Zähler und Galerie

JavaScript Im Browser 3 Min Lesezeit 3 BeispieleLektion 8 von 9

Dieser Abschnitt endet mit dem Muster, das den Rest des Kurses trägt und das Frameworks später automatisieren. Es besteht aus drei Teilen und passt in einen Satz.

Zustand, Ereignis, Zeichnen

Zustand, Ereignis, Zeichnen
// 1. Der Zustand. Eine einzige Wahrheit, hier eine Zahl.
let stand = 0;
const MAX = 3;

const anzeige = document.getElementById("anzeige");

// 2. Die Zeichenfunktion. Sie liest den Zustand und
//    schreibt die Anzeige. Sie ändert nichts.
function zeichne() {
  anzeige.textContent = String(stand);
  document.getElementById("minus").disabled = stand === 0;
  document.getElementById("plus").disabled = stand === MAX;
}

// 3. Die Ereignisse. Sie ändern nur den Zustand und
//    rufen danach die Zeichenfunktion auf.
document.getElementById("plus").addEventListener("click", () => {
  stand = Math.min(stand + 1, MAX);
  zeichne();
});

document.getElementById("minus").addEventListener("click", () => {
  stand = Math.max(stand - 1, 0);
  zeichne();
});

// Auch der Anfangszustand wird gezeichnet und nicht ins
// Markup geschrieben. Sonst stehen zwei Wahrheiten da.
zeichne();
console.log("Drück die Knöpfe bis an beide Grenzen.");

Es gibt einen Zustand. Eine Variable, ein Objekt, ein Array. Er ist die einzige Wahrheit darüber, wie die Sache gerade steht.

Es gibt Ereignisse, die ihn ändern. Ein Klick erhöht eine Zahl, ein Eingabefeld setzt einen Filter. Mehr tun sie nicht: Sie fassen die Anzeige nicht an.

Es gibt eine Funktion, die aus dem Zustand die Anzeige macht. Sie liest, schreibt in den DOM und ändert am Zustand nichts.

Der Handler ist damit immer zwei Zeilen lang: Zustand ändern, zeichnen. Was auf dem Bildschirm passiert, steht an genau einer Stelle.

Diese zwei Zeilen kannst du in der Eingabezeile der Console von Hand fahren, denn stand und zeichne gehören zur Seite. stand antwortet 0. Tipp stand = 3: Die Antwort ist 3, aber in der Anzeige steht weiter die alte Zahl, denn den Zustand zu ändern zeichnet nichts. Erst zeichne() holt die Anzeige nach, und der Plus-Knopf ist danach gesperrt, weil MAX erreicht ist. Zwei Schritte, und der Handler tut genau diese zwei.

Auch der Anfangszustand wird gezeichnet. Im ersten Beispiel steht am Ende ein einzelner Aufruf von zeichne(). Ohne den müsste das Markup den Startwert doppelt führen, einmal als Text und einmal in der Variablen, und beim nächsten Ändern läuft eines von beiden weg.

Nie die Anzeige zurücklesen

Die Anzeige als Zustand missbraucht
let stand = 0;

document.getElementById("knopf").addEventListener("click", () => {
  // So: der Zustand ist eine Variable, die Anzeige folgt.
  stand = stand + 1;
  document.getElementById("a").textContent = String(stand);

  // So nicht: der Zustand wird aus dem Text zurückgelesen.
  // Es funktioniert, solange dort nur eine nackte Zahl steht.
  const b = document.getElementById("b");
  const alt = Number(b.textContent);
  b.textContent = `${alt + 1} Punkte`;

  console.log("sauber:", stand, "| aus dem Text:", Number(b.textContent));
});

// Beim ersten Klick geht es schief: aus "0 Punkte" macht
// Number ein NaN, und ab da steht dort NaN Punkte.
console.log("Drück den Knopf zweimal.");

Der verlockende Kurzschluss sieht so aus: Die Zahl steht doch schon im Element, warum sie zusätzlich in einer Variablen führen?

Weil die Anzeige für Menschen gemacht ist und nicht für das Programm. Im zweiten Beispiel steht dort 0 Punkte statt 0, und Number("0 Punkte") ist NaN. Ab dem ersten Klick steht dort NaN Punkte, und der Fehler sitzt nicht dort, wo er auffällt.

Selbst wenn es zunächst gutgeht, ist es eine Falle mit Zeitzünder: Sobald jemand die Darstellung ändert, ein Tausendertrennzeichen einbaut, eine Einheit anhängt oder die Zahl in ein anderes Element verschiebt, bricht die Logik. Und dieser Jemand hat keinen Grund anzunehmen, dass am Text eines Absatzes eine Rechnung hängt.

Die Anzeige darf immer aus dem Zustand entstehen, nie umgekehrt.

Die Ränder

Die Ränder einer Galerie
let index = 0;
const bilder = document.querySelectorAll(".bild");
const zurueck = document.getElementById("zurueck");
const weiter = document.getElementById("weiter");

function zeichne() {
  // Genau eines trägt die Klasse. toggle mit zweitem
  // Argument macht daraus eine Zeile.
  bilder.forEach((bild, i) => {
    bild.classList.toggle("ist-aktiv", i === index);
  });

  // Die Ränder: am Anfang geht es nicht zurück, am Ende
  // nicht weiter. Gesperrt statt wirkungslos, damit man
  // es sieht.
  zurueck.disabled = index === 0;
  weiter.disabled = index === bilder.length - 1;
}

weiter.addEventListener("click", () => {
  index = Math.min(index + 1, bilder.length - 1);
  zeichne();
});

zurueck.addEventListener("click", () => {
  index = Math.max(index - 1, 0);
  zeichne();
});

zeichne();
console.log("Blätter bis an beide Enden.");

Eine Galerie hat zwei Stellen, an denen etwas nicht mehr geht: das erste und das letzte Bild.

Beides muss der Zustand abfangen, sonst wird der Index negativ oder zu groß und bilder[index] ist undefined. Math.min und Math.max erledigen das in derselben Zeile, in der auch gerechnet wird.

Zum Abfangen gehört aber noch etwas: Der Knopf soll auch aussehen, als ginge es nicht. Ein Knopf, der da ist und nichts tut, ist schlechter als einer, der gesperrt ist. Deshalb setzt die Zeichenfunktion disabled, und zwar aus demselben Zustand heraus wie alles andere.

Die Alternative wäre, im Kreis zu blättern, also vom letzten Bild wieder zum ersten. Beides ist vertretbar, und die Entscheidung gehört an den Anfang, nicht ans Ende.

Und wie es weitergeht

Aus 11.8 kennst du die Aufteilung in Daten holen, Element bauen, Liste zeichnen. Hier kommt der vierte Teil dazu: der Zustand, der sich ändert.

Zusammen ergibt das eine Anwendung. Was in den restlichen Abschnitten dazukommt, ändert daran nichts Grundsätzliches: Abschnitt 13 füllt den Zustand aus einem Formular, Abschnitt 16 aus dem Netz, Abschnitt 17 speichert ihn im Browser. Gezeichnet wird jedes Mal auf dieselbe Art.

Zum Mitnehmen

Es gibt einen Zustand, es gibt Ereignisse, die ihn ändern, und es gibt eine Funktion, die aus dem Zustand die Anzeige macht. Das trägt den Rest des Kurses.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe im Editor, direkt im Browser geprüft

    Öffnet sich mit dem Basis Konto.