mitmario.dev

Synthese: die Kartenliste

JavaScript Im Browser 3 Min Lesezeit 3 BeispieleLektion 8 von 9

Alles aus diesem Abschnitt in einem Stück: Daten auswählen, Elemente bauen, einhängen, und einen Satz dazuschreiben, der zusammenfasst, was da steht.

Drei Funktionen, drei Aufgaben

Der Schnitt in drei Funktionen
const daten = [
  { id: "html", titel: "HTML", preisCent: 0 },
  { id: "js", titel: "JavaScript", preisCent: 4900 },
];

const euro = new Intl.NumberFormat("de-DE", {
  style: "currency",
  currency: "EUR",
});

// 1. Aus einem Datensatz ein Element. Kennt die Seite nicht.
function baueKarte(kurs) {
  const karte = document.createElement("article");
  karte.classList.add("karte");
  karte.dataset.id = kurs.id;

  const titel = document.createElement("h3");
  titel.textContent = kurs.titel;

  const preis = document.createElement("p");
  preis.classList.add("preis");
  preis.textContent = euro.format(kurs.preisCent / 100);

  karte.append(titel, preis);
  return karte;
}

// 2. Aus einer Liste eine Ansicht. Kennt das Ziel nur als Parameter.
function zeichne(liste, ziel) {
  ziel.replaceChildren();
  const sammler = document.createDocumentFragment();
  for (const kurs of liste) sammler.append(baueKarte(kurs));
  ziel.append(sammler);
}

// 3. Aus einer Liste ein Satz. Fasst gar nichts an.
function fasseZusammen(liste) {
  const summe = liste.reduce((s, kurs) => s + kurs.preisCent, 0);
  return `${liste.length} Kurse, ${euro.format(summe / 100)} gesamt`;
}

zeichne(daten, document.getElementById("ziel"));
document.getElementById("fuss").textContent = fasseZusammen(daten);

console.log(baueKarte(daten[0]).outerHTML);
console.log(fasseZusammen(daten));

Der Schnitt ist immer derselbe, und er ist wichtiger als jede einzelne Zeile darin.

baueKarte(kurs) macht aus einem Datensatz ein Element und gibt es zurück. Sie sucht nichts auf der Seite, sie hängt nichts ein, sie weiß nicht einmal, dass es eine Seite gibt.

zeichne(liste, ziel) kennt das Ziel, aber nur als Parameter. Sie leert, läuft über die Daten, sammelt und hängt einmal ein.

fasseZusammen(liste) rechnet und gibt einen Text zurück. Sie fasst gar nichts an.

Nur die letzten beiden Zeilen des Programms verbinden das Ganze mit der Seite. Alles darüber ist unabhängig davon, wie die Seite aussieht.

Warum das die Mühe wert ist

Dieselbe Funktion, einmal falsch geschnitten
const daten = [{ id: "js", titel: "JavaScript" }];

// So nicht: sie baut UND hängt ein. Beides in einer Funktion.
function baueUndHaenge(kurs) {
  const karte = document.createElement("article");
  karte.textContent = kurs.titel;
  document.getElementById("ziel").append(karte);
}

baueUndHaenge(daten[0]);

// Der Preis dafür zeigt sich sofort: es gibt nichts zurück.
console.log("Rückgabe:", baueUndHaenge(daten[0]));

// Man kann sie nicht anderswo hinzeichnen lassen.
// Man kann sie nicht prüfen, ohne die Seite mitzudenken.
// Und man kann sie nicht in einem Fragment sammeln,
// denn sie hängt sich immer selbst in dasselbe Ziel.
console.log("Karten im Ziel:", document.getElementById("ziel").children.length);

Im zweiten Beispiel steht dieselbe Sache falsch herum: eine Funktion, die baut und einhängt. Sie funktioniert, und trotzdem verliert sie vier Dinge auf einmal.

Sie gibt nichts zurück. Wer sie aufruft, bekommt undefined und kann mit dem Ergebnis nichts weiter tun.

Sie kann nur an eine Stelle zeichnen. Ein zweites Ziel, eine Seitenleiste, ein Ausdruck: alles nicht möglich, ohne die Funktion zu ändern.

Sie ist nicht prüfbar. In der Aufgabe zu dieser Lektion wird baueKarte einzeln aufgerufen und ihr Rückgabewert betrachtet. Bei dieser Fassung gäbe es dafür nichts.

Sie kann nicht sammeln. Das DocumentFragment aus Lektion 11.7 setzt voraus, dass jemand anders entscheidet, wohin das Element kommt.

In der Eingabezeile der Console siehst du den Unterschied in zwei Zeilen. Im ersten Beispiel antwortet baueKarte({ id: "php", titel: "PHP", preisCent: 3900 }) mit <article class="karte">, und auf der Seite ändert sich nichts. Im zweiten antwortet baueUndHaenge({ titel: "PHP" }) mit undefined, und auf der Seite steht eine Karte mehr. Das eine ist eine Antwort, das andere eine Nebenwirkung. Und fasseZusammen([]) antwortet "0 Kurse, 0,00 € gesamt", ohne dass dafür eine Seite nötig wäre.

Die Regel dahinter ist keine Geschmacksfrage: eine Funktion, die etwas herstellt, gibt es zurück, statt es abzuliefern. Lektion 20.2 macht daraus ein eigenes Thema.

Erst filtern, dann sortieren

Filtern, sortieren, zeichnen
const kurse = [
  { id: "js", titel: "JavaScript", status: "live" },
  { id: "node", titel: "Node", status: "geplant" },
  { id: "css", titel: "CSS", status: "live" },
];

function zeichne(liste, ziel) {
  ziel.replaceChildren();

  // Erst auswählen, dann ordnen, dann bauen. In dieser Reihenfolge,
  // weil sortieren teurer ist als filtern.
  const sichtbar = liste
    .filter((kurs) => kurs.status === "live")
    .sort((a, b) => a.titel.localeCompare(b.titel));

  for (const kurs of sichtbar) {
    const el = document.createElement("article");
    el.dataset.id = kurs.id;
    el.textContent = kurs.titel;
    ziel.append(el);
  }
}

zeichne(kurse, document.getElementById("ziel"));
console.log(document.getElementById("ziel").innerHTML);

// filter hat eine neue Liste gemacht, das Original ist unberührt.
console.log("Original:", kurse.map((k) => k.titel).join(", "));

Die Reihenfolge im dritten Beispiel ist Absicht: auswählen, ordnen, bauen.

Zuerst filter, denn was wegfällt, muss man nicht mehr sortieren. Sortieren ist die teuerste der drei Arbeiten, und bei einer langen Liste macht das einen Unterschied.

Dann sort. Achtung an dieser Stelle: sort verändert das Array, auf dem es aufgerufen wird. Hier ist das ungefährlich, weil filter ohnehin schon eine neue Liste gemacht hat. Wer ohne Filter sortiert, macht vorher eine Kopie mit [...liste], sonst steht das Original hinterher anders da als vorher, und irgendwo verlässt sich jemand darauf.

Dann bauen und einhängen.

Der Ausblick

Was hier steht, ist bereits die Hälfte einer Anwendung. Es fehlt nur eines: Bisher entscheidet der Code beim Laden, was gezeichnet wird, und danach passiert nichts mehr.

In Abschnitt 12 kommen die Klicks dazu. Und weil die drei Funktionen sauber geschnitten sind, wird das kein Umbau: Es kommt eine Stelle hinzu, die den Zustand ändert, und danach wird zeichne einfach noch einmal aufgerufen. Das data-id an jeder Karte ist dafür schon vorbereitet.

Zum Mitnehmen

Die bauende Funktion gibt ein Element zurück und hängt es nicht ein. Genau daran hängt, ob sie sich prüfen und wiederverwenden lässt.

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.