modulo 11 di 24 / Ore 21-40

Moduli 9-16 / Checkpoint 40 ore

Lista strumenti e filtri di ricerca

Costruire la lista strumenti con filtri per nome, categoria e stato nella Presentation, senza duplicare logica applicativa né copiare regole del dominio nella UI.

30 min Architettura del software

Lista strumenti e filtri di ricerca

Questa lezione appartiene al Modulo 11 — Presentation: l’interfaccia come bordo del sistema.

Cosa deve fare questa parte della UI

La lista strumenti è una delle viste principali del Registro. L’utente deve poter:

  • vedere tutti gli strumenti disponibili
  • filtrare per nome (ricerca testuale)
  • filtrare per categoria
  • filtrare per stato (disponibile, in prestito, guasto, in manutenzione)

La UI non deve decidere quali strumenti possono essere prestati — quella è una regola del dominio. La UI mostra i risultati e lascia che l’utente scelga.

Struttura HTML minima

<!-- Lista strumenti con filtri -->
<section id="lista-strumenti">
  <div class="filtri">
    <input type="text" id="filtro-nome" placeholder="Cerca per nome..." />
    <select id="filtro-categoria">
      <option value="">Tutte le categorie</option>
      <option value="Elettrico">Elettrico</option>
      <option value="Manuale">Manuale</option>
      <option value="Misura">Misura</option>
    </select>
    <select id="filtro-stato">
      <option value="">Tutti gli stati</option>
      <option value="disponibile">Disponibile</option>
      <option value="in_prestito">In prestito</option>
      <option value="guasto">Guasto</option>
      <option value="in_manutenzione">In manutenzione</option>
    </select>
  </div>
  <ul id="elenco-strumenti"></ul>
</section>

La logica dei filtri — dove sta?

Il filtro è una logica applicativa: riceve la lista completa e restituisce un sottoinsieme. Appartiene all’Application — non alla UI.

// application/cercaStrumenti.js
function creaCercaStrumenti({ archivioStrumenti }) {
  return function cercaStrumenti({ query = "", categoria = "", stato = "" }) {
    const tutti = archivioStrumenti.leggiTutti();

    return tutti.filter(s => {
      const corrispondeName = !query ||
        s.nome.toLowerCase().includes(query.toLowerCase());
      const corrispondeCategoria = !categoria || s.categoria === categoria;
      const corrispondeStato = !stato || s.stato === stato;
      return corrispondeName && corrispondeCategoria && corrispondeStato;
    });
  };
}

Questa funzione può essere testata senza browser: riceve un array e restituisce un array filtrato.

La UI — solo lettura dell’input e rendering

// presentation/listaStrumenti.js
function avviaListaStrumenti({ cercaStrumenti }) {
  const inputNome = document.getElementById("filtro-nome");
  const selectCategoria = document.getElementById("filtro-categoria");
  const selectStato = document.getElementById("filtro-stato");
  const elenco = document.getElementById("elenco-strumenti");

  function aggiorna() {
    const filtri = {
      query: inputNome.value,
      categoria: selectCategoria.value,
      stato: selectStato.value
    };

    const risultati = cercaStrumenti(filtri);
    renderListaStrumenti(elenco, risultati);
  }

  // Aggiorna al cambio di qualsiasi filtro
  inputNome.addEventListener("input", aggiorna);
  selectCategoria.addEventListener("change", aggiorna);
  selectStato.addEventListener("change", aggiorna);

  // Prima visualizzazione
  aggiorna();
}

La UI non filtra — chiama cercaStrumenti con i filtri letti dall’input. La logica sta nel caso d’uso.

Rendere la lista — senza logica di dominio

// presentation/listaStrumenti.js
function renderListaStrumenti(contenitore, strumenti) {
  if (strumenti.length === 0) {
    contenitore.innerHTML = "<li>Nessuno strumento trovato.</li>";
    return;
  }

  contenitore.innerHTML = strumenti
    .map(s => `
      <li class="strumento strumento--${s.stato}">
        <span class="strumento__nome">${s.nome}</span>
        <span class="strumento__categoria">${s.categoria}</span>
        <span class="strumento__stato">${etichettaStato(s.stato)}</span>
      </li>
    `)
    .join("");
}

// Traduzione degli stati per la UI — appartiene alla Presentation
function etichettaStato(stato) {
  const etichette = {
    "disponibile": "Disponibile",
    "in_prestito": "In prestito",
    "guasto": "Guasto",
    "in_manutenzione": "In manutenzione"
  };
  return etichette[stato] ?? stato;
}

Nota: etichettaStato è una funzione di presentazione — traduce i valori tecnici in etichette leggibili. Non è una regola del dominio.

Cosa NON fare nella UI dei filtri

// ❌ Logica di dominio nella UI — la UI non deve sapere queste regole
function renderListaStrumenti(contenitore, strumenti) {
  strumenti.forEach(s => {
    const li = document.createElement("li");
    // ← Questa condizione è una regola del dominio — non appartiene qui
    if (s.stato === "guasto" || s.stato === "in_prestito" || s.stato === "in_manutenzione") {
      li.classList.add("non-prestabile");
    }
    contenitore.appendChild(li);
  });
}

// ✅ La UI mostra solo — il caso d'uso decide cosa è prestabile
function renderListaStrumenti(contenitore, strumenti) {
  strumenti.forEach(s => {
    const li = document.createElement("li");
    li.classList.add(`strumento--${s.stato}`); // la classe riflette lo stato, non la regola
    contenitore.appendChild(li);
  });
}

Composizione in main.js

// main.js
const archivioStrumenti = creaArchivioStrumentiLocalStorage();
const cercaStrumenti = creaCercaStrumenti({ archivioStrumenti });

avviaListaStrumenti({ cercaStrumenti });

Procedura guidata

  1. Crea l’HTML con i tre filtri (nome, categoria, stato).
  2. Crea la funzione cercaStrumenti nell’Application con i tre parametri.
  3. Crea la funzione avviaListaStrumenti nella Presentation che ascolta i filtri e chiama cercaStrumenti.
  4. Crea renderListaStrumenti che trasforma l’array in HTML.
  5. Componi in main.js.
  6. Verifica: filtra per nome, per categoria, per stato — i risultati cambiano correttamente?

Attività in classe

## Lista strumenti con filtri

HTML creato (sì/no):

Funzione cercaStrumenti creata in Application (sì/no):

Filtri implementati: nome (sì/no) | categoria (sì/no) | stato (sì/no)

Logica di dominio trovata nella UI (da spostare):

Verifica eseguita:

Spiegazione guidata per studiare

La lista strumenti con filtri è un esempio classico di come la Presentation e l’Application collaborano: la UI raccoglie i filtri, l’Application li applica alla lista, la UI mostra i risultati.

Il punto centrale è: la logica del filtro appartiene all’Application, non alla UI. Se il filtro stesse nell’event listener, diventerebbe impossibile testarlo senza browser. Se stesse nel dominio, il dominio conoscerebbe dettagli dell’interfaccia (query di ricerca, selezione della categoria) che non lo riguardano.

Il rischio tipico è scrivere strumenti.filter(...) direttamente nell’event listener. Funziona, ma se domani si aggiunge un quarto filtro, bisogna toccare la UI. Con la funzione cercaStrumenti nell’Application, si aggiunge il parametro lì — la UI non cambia.

Passo per passo nel progetto

  1. Verifica che la funzione cercaStrumenti esista nell’Application.
  2. Verifica che accetti query, categoria e stato come parametri opzionali.
  3. Verifica che la UI chiami cercaStrumenti con i valori dei filtri — senza filtrare da sola.
  4. Verifica che renderListaStrumenti non contenga condizioni sulle regole di dominio.
  5. Testa con filtri combinati: nome + stato, categoria + stato.

Esempio da leggere lentamente

// Questo è il confine corretto:

// UI → raccoglie i filtri dall'input
const filtri = {
  query: document.getElementById("filtro-nome").value,
  categoria: document.getElementById("filtro-categoria").value,
  stato: document.getElementById("filtro-stato").value
};

// Application → applica i filtri alla lista
const risultati = cercaStrumenti(filtri);

// UI → mostra i risultati (solo rendering, nessuna logica)
renderListaStrumenti(document.getElementById("elenco"), risultati);

Tre responsabilità, tre pezzi di codice separati.

Prova di comprensione

  • Se aggiungi un quarto filtro (es. per data di ultimo prestito), dove lo aggiungi? In quale file?
  • La funzione cercaStrumenti può essere testata senza aprire il browser?
  • Dove metti etichettaStato? Perché non nel dominio?

Errori da evitare

  • filtrare la lista direttamente nell’event listener;
  • scrivere if (s.stato === "guasto") { ... } nella funzione di rendering — è una regola del dominio;
  • chiamare archivioStrumenti.leggiTutti() direttamente dalla UI;
  • non gestire il caso lista vuota (lo stato vuoto deve essere visibile).

Prodotto da consegnare

Può essere:

  • la funzione cercaStrumenti con almeno tre parametri di filtro;
  • la lista strumenti funzionante con filtro per nome in tempo reale;
  • un test manuale documentato: filtra per stato “guasto” — solo gli strumenti guasti appaiono.

Checklist di chiusura

  • I filtri sono gestiti da cercaStrumenti nell’Application
  • La UI raccoglie i valori dei filtri e li passa al caso d’uso
  • renderListaStrumenti non contiene condizioni sulle regole del dominio
  • Lo stato vuoto (nessun risultato) viene mostrato con un messaggio
  • I filtri combinati funzionano correttamente

Domande per la revisione

  • La funzione cercaStrumenti è testabile senza browser?
  • Se aggiungi un filtro, devi toccare più di un file?
  • Hai trovato logica di dominio nella funzione di rendering?

Risultato atteso

Alla fine lo studente deve saper dire:

La lista strumenti mostra tutti gli strumenti e permette di filtrare per [nome/categoria/stato].
Il filtro è implementato in [file/funzione] nell'Application.
La UI legge i filtri e chiama [cercaStrumenti] senza filtrare da sola.
Ho verificato che filtrare per [stato] mostra solo gli strumenti corretti.

Se questa spiegazione non è possibile, la lezione non è ancora davvero conclusa.