Applicazione dei principi di programmazione delle mini app a un progetto di esempio

Il dominio dell'app

Per mostrare il modo di programmazione delle mini app applicato a un'app web, avevo bisogno di un'idea piccola ma sufficientemente completa. L'allenamento a intervalli ad alta intensità (HIIT) è una strategia di allenamento cardiovascolare che prevede l'alternanza di serie di brevi periodi di esercizio anaerobico intenso con periodi di recupero meno intensi. Molti allenamenti HIIT utilizzano timer HIIT, ad esempio questa sessione online di 30 minuti del canale YouTube The Body Coach TV.

sessione online di allenamento HIIT con timer verde ad alta intensità.
Periodo attivo.
Sessione online di allenamento HIIT con timer rosso a bassa intensità.
Periodo di riposo.

App di esempio HIIT Time

Per questo capitolo, ho creato un esempio di base di un'applicazione timer HIIT chiamata "HIIT Time" che consente all'utente di definire e gestire vari timer, costituiti sempre da un intervallo di intensità elevata e bassa, per poi selezionarne uno per una sessione di allenamento. È un'app adattabile con una barra di navigazione, una barra delle schede e tre pagine:

  • Allenamento: la pagina attiva durante un allenamento. Consente all'utente di selezionare uno dei timer e offre tre squilli di avanzamento: numero di serie, ciclo attivo e periodo di riposo.
  • Timer: gestisce i timer esistenti e consente all'utente di crearne di nuovi.
  • Preferenze: consente di attivare/disattivare gli effetti sonori e l'output vocale e di selezionare la lingua e il tema.

Gli screenshot seguenti danno un'idea dell'applicazione.

App di esempio HIIT Time in modalità Ritratto.
Scheda "Allenamento" di HIIT Time in modalità Ritratto.
App di esempio HIIT Time in modalità Orizzontale.
scheda "Allenamento" con l'ora dell'allenamento HIIT in modalità Orizzontale.
App di esempio HIIT Time che mostra la gestione di un timer.
Gestione del timer HIIT Time.

Struttura dell'app

Come descritto sopra, l'app è costituita da una barra di navigazione, una barra delle schede e tre pagine disposte in una griglia. La barra di navigazione e la barra delle schede sono realizzate come iframe con un contenitore <div> tra di loro con altri tre iframe per le pagine, di cui uno è sempre visibile e dipende dalla selezione attiva nella barra delle schede. Un iframe finale che rimanda a about:blank serve per le pagine in-app create dinamicamente, necessarie per modificare i timer esistenti o crearne di nuovi. Chiedo a questo pattern di app a pagina singola con più pagine (MPSPA).

Visualizzazione di DevTools di Chrome della struttura HTML dell&#39;app che mostra che è composta da sei iframe: uno per la barra di navigazione, uno per la barra delle schede e tre raggruppati per ogni pagina dell&#39;app, con un iframe segnaposto finale per le pagine dinamiche.
L'app è costituita da sei iframe.

Markup lit-html basato su componenti

La struttura di ogni pagina è realizzata come uno scafo lit-html che viene valutato dinamicamente in fase di esecuzione. Per una spiegazione di lit-html, è una libreria di modelli HTML efficiente, espressiva ed estensibile per JavaScript. Se lo utilizzi direttamente nei file HTML, il modello di programmazione mentale è direttamente orientato all'output. In qualità di programmatore, scrivi un modello dell'output finale, mentre lit-html colma le lacune in modo dinamico in base ai tuoi dati e collega gli ascoltatori di eventi. L'app utilizza elementi personalizzati di terze parti come <sl-progress-ring> di Shoelace o un elemento personalizzato implementato autonomamente <human-duration>. Poiché gli elementi personalizzati hanno un'API dichiarativa (ad esempio l'attributo percentage dell'anello di avanzamento), funzionano bene con lit-html, come puoi vedere nell'elenco di seguito.

<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>
Tre pulsanti e un anello di avanzamento.
Sezione della pagina visualizzata corrispondente al markup riportato sopra.

Modello di programmazione

Ogni pagina ha una classe Page corrispondente che riempie il markup lit-html con informazioni utili fornendo le implementazioni dei gestori di eventi e i dati per ogni pagina. Questo tipo supporta anche i metodi di ciclo di vita come onShow(), onHide(), onLoad() e onUnload(). Le pagine hanno accesso a un data store che serve per condividere lo stato per pagina e lo stato globale, eventualmente mantenuti. Poiché tutte le stringhe sono gestite centralmente, l'internazionalizzazione è integrata. Il routing viene gestito dal browser essenzialmente senza costi, poiché l'app si limita a attivare/disattivare la visibilità dell'iframe e per le pagine create dinamicamente modifica l'attributo src dell'iframe segnaposto. L'esempio seguente mostra il codice per chiudere una pagina creata dinamicamente.

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

const page = new Page({
  eventHandlers: {
    back: (e) => {
      e.preventDefault();
      window.top.history.back();
    },
  },
});
Pagina in-app realizzata come iframe.
La navigazione avviene da un iframe all'altro.

Stili

La definizione dello stile delle pagine avviene singolarmente in un proprio file CSS con ambito. Ciò significa che in genere gli elementi possono essere indirizzati direttamente tramite i relativi nomi, poiché non possono verificarsi conflitti con altre pagine. Gli stili globali vengono aggiunti a ogni pagina, pertanto le impostazioni centrali come font-family o box-sizing non devono essere dichiarate ripetutamente. Qui vengono definiti anche i temi e le opzioni della modalità Buio. L'elenco seguente mostra le regole per la pagina Preferenze che organizzano i vari elementi del modulo su una griglia.

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;
}
Pagina delle preferenze dell&#39;app HIIT Time che mostra un modulo in layout a griglia.
Ogni pagina è un mondo a sé. Lo stile viene applicato direttamente ai nomi degli elementi.

Attivazione schermo

Durante un allenamento, lo schermo non deve spegnersi. Sui browser che lo supportano, HIIT Time lo realizza tramite un blocco di attivazione dello schermo. Lo snippet riportato di seguito mostra come eseguire questa operazione.

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();
    }
  });
}

Test dell'applicazione

L'applicazione HIIT Time è disponibile su GitHub. Puoi provare la demo in una nuova finestra o direttamente nell'iframe incorporato di seguito, che simula un dispositivo mobile.

Ringraziamenti

Questo articolo è stato esaminato da Joe Medley, Kayce Basques, Milica Mihajlija, Alan Kent, e Keith Gu.