Anwendung der Programmierprinzipien für Mini-Apps auf ein Beispielprojekt

Die App-Domain

Um die Programmierung von Mini-Apps auf eine Web-App anzuwenden, brauchte ich eine kleine, aber vollständige App-Idee. Hochintensives Intervalltraining (HIIT) ist eine Strategie für Herz-Kreislauf-Übungen, bei der sich kurze Phasen intensiver anaerober Übungen mit weniger intensiven Erholungsphasen abwechseln. Viele HIIT-Trainings verwenden HIIT-Timer, z. B. diese 30-minütige Online-Sitzung vom YouTube-Kanal The Body Coach TV.

Online-HIIT-Trainingseinheit mit grünem Timer für hohe Intensität.
Aktive Phase.
HIIT-Trainingseinheit online mit rotem Timer für geringe Intensität.
Ruhephase.

Beispiel-App „HIIT Time“

Für dieses Kapitel habe ich ein einfaches Beispiel für eine solche HIIT-Timer-Anwendung mit dem passenden Namen „HIIT Time“ erstellt. Damit kann der Nutzer verschiedene Timer definieren und verwalten, die immer aus einem Intervall mit hoher und einem mit niedriger Intensität bestehen. Anschließend kann er einen davon für eine Trainingseinheit auswählen. Es handelt sich um eine responsive App mit einer Navigationsleiste, einer Tabbar und drei Seiten:

  • Workout:Die aktive Seite während eines Workouts. Hier kann der Nutzer einen der Timer auswählen. Außerdem gibt es drei Fortschrittsringe: die Anzahl der Sätze, die aktive Phase und die Ruhephase.
  • Timers:Hier werden vorhandene Timer verwaltet und neue erstellt.
  • Preferences:Hier können Soundeffekte und Sprachausgabe aktiviert und deaktiviert sowie Sprache und Design ausgewählt werden.

Die folgenden Screenshots geben einen Eindruck von der Anwendung.

Beispiel-App „HIIT Time“ im Porträtmodus
Tab „Workout“ von HIIT Time im Hochformat
Beispiel-App „HIIT Time“ im Querformat
Tab „Workout“ von HIIT Time im Querformat
Beispiel-App „HIIT Time“ mit Timerverwaltung.
Timerverwaltung von HIIT Time

App-Struktur

Wie oben beschrieben, besteht die App aus einer Navigationsleiste, einer Tabbar und drei Seiten, die in einem Raster angeordnet sind. Navigationsleiste und Tabbar werden als iFrames mit einem <div> Container dazwischen realisiert. Außerdem gibt es drei weitere iFrames für die Seiten, von denen immer einer sichtbar ist und von der aktiven Auswahl in der Tabbar abhängt. Ein letzter iFrame, der auf about:blank verweist, dient für dynamisch erstellte In-App-Seiten, die zum Ändern vorhandener oder zum Erstellen neuer Timer erforderlich sind. Ich nenne dieses Muster „Multi-Page Single-Page App“ (MPSPA).

Chrome-Entwicklertools-Ansicht der HTML-Struktur der App, die aus sechs iFrames besteht: eines für die Navigationsleiste, eines für die Tab-Leiste und drei gruppierte für jede Seite der App, mit einem letzten Platzhalter-iFrame für dynamische Seiten.
Die App besteht aus sechs iFrames.

Komponentenbasiertes lit-html-Markup

Die Struktur jeder Seite wird als lit-html-Gerüst , das zur Laufzeit dynamisch ausgewertet wird, realisiert. Lit-html ist eine effiziente, ausdrucksstarke und erweiterbare HTML-Templating-Bibliothek für JavaScript. Wenn Sie sie direkt in den HTML-Dateien verwenden, ist das mentale Programmiermodell direkt ausgabeorientiert. Als Programmierer schreiben Sie eine Vorlage für die endgültige Ausgabe. Lit-html füllt dann die Lücken dynamisch anhand Ihrer Daten und verknüpft die Event-Listener. Die App verwendet benutzerdefinierte Elemente von Drittanbietern wie Shoelace's <sl-progress-ring> oder ein selbst implementiertes benutzerdefiniertes Element namens <human-duration>. Da benutzerdefinierte Elemente eine deklarative API haben (z. B. das Attribut percentage des Fortschrittsrings), funktionieren sie gut mit lit-html, wie Sie in der folgenden Auflistung sehen können.

<div>
  <button class="start" @click="${eventHandlers.start}" type="button">
    ${strings.START}
  </button>
  <button class="pause" @click="${eventHandlers.pause}" type="button">
    ${strings.PAUSE}
  </button>
  <button class="reset" @click="${eventHandlers.reset}" type="button">
    ${strings.RESET}
  </button>
</div>

<div class="progress-rings">
  <sl-progress-ring
    class="sets"
    percentage="${Math.floor(data.sets/data.activeTimer.sets*100)}"
  >
    <div class="progress-ring-caption">
      <span>${strings.SETS}</span>
      <span>${data.sets}</span>
    </div>
  </sl-progress-ring>
</div>
Drei Schaltflächen und ein Fortschrittsring.
Gerenderter Abschnitt der Seite, der dem Markup oben entspricht.

Programmiermodell

Jede Seite hat eine entsprechende Page-Klasse, die das lit-html-Markup mit Leben füllt, indem sie Implementierungen der Event-Handler und die Daten für jede Seite bereitstellt. Diese Klasse unterstützt auch Lebenszyklusmethoden wie onShow(), onHide(), onLoad() und onUnload(). Seiten haben Zugriff auf einen Datenspeicher, der zum Freigeben von optional persistentem seitenbezogenem und globalem Status dient. Alle Strings werden zentral verwaltet, sodass die Internationalisierung integriert ist. Das Routing wird im Wesentlichen kostenlos vom Browser übernommen, da die App nur die Sichtbarkeit von iFrames umschaltet und für dynamisch erstellte Seiten das Attribut src des Platzhalter-iFrames ändert. Das folgende Beispiel zeigt den Code zum Schließen einer dynamisch erstellten Seite.

import Page from '../page.js';

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
In-App-Seite als iFrame realisiert.
Die Navigation erfolgt von iFrame zu iFrame.

Stile

Die Stile der Seiten werden seitenweise in einer eigenen CSS-Datei mit Bereich festgelegt. Das bedeutet, dass Elemente in der Regel direkt über ihre Elementnamen angesprochen werden können, da keine Konflikte mit anderen Seiten auftreten können. Globale Stile werden jeder Seite hinzugefügt, sodass zentrale Einstellungen wie font-family oder box-sizing nicht wiederholt deklariert werden müssen. Hier werden auch die Designs und Optionen für den dunklen Modus definiert. Die folgende Auflistung zeigt die Regeln für die Seite „Preferences“, auf der die verschiedenen Formularelemente in einem Raster angeordnet sind.

main {
  max-width: 600px;
}

form {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-gap: 0.5rem;
  margin-block-end: 1rem;
}

label {
  text-align: end;
  grid-column: 1 / 2;
}

input,
select {
  grid-column: 2 / 3;
}
Die Seite mit den Einstellungen der HIIT Time App mit einem Formular im Rasterlayout.
Jede Seite ist eine eigene Welt. Die Stile werden direkt mit den Elementnamen festgelegt.

Bildschirm-Wakelock

Während eines Workouts sollte sich der Bildschirm nicht ausschalten. In Browsern, die dies unterstützen, wird dies in HIIT Time durch einen Bildschirm-Wakelock realisiert. Das folgende Snippet zeigt, wie das geht.

if ('wakeLock' in navigator) {
  const requestWakeLock = async () => {
    try {
      page.shared.wakeLock = await navigator.wakeLock.request('screen');
      page.shared.wakeLock.addEventListener('release', () => {
        // Nothing.
      });
    } catch (err) {
      console.error(`${err.name}, ${err.message}`);
    }
  };
  // Request a screen wake lock…
  await requestWakeLock();
  // …and re-request it when the page becomes visible.
  document.addEventListener('visibilitychange', async () => {
    if (
      page.shared.wakeLock !== null &&
      document.visibilityState === 'visible'
    ) {
      await requestWakeLock();
    }
  });
}

Anwendung testen

Die HIIT Time-Anwendung ist auf GitHub verfügbar. Sie können die Demo in einem neuen Fenster oder direkt im unten eingebetteten iFrame ausprobieren, das ein Mobilgerät simuliert.

Danksagungen

Dieser Artikel wurde von Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent, und Keith Gu überprüft.