Progettare Tabelle HTML Interattive con Ordinamento e Filtri Progressivi

by Anastasia P.
SHARE
Progettare Tabelle HTML Interattive con Ordinamento e Filtri Progressivi
© Guida-HTML5.it

Introduzione

Le tabelle HTML sono adatte a rappresentare dati organizzati in righe e colonne, ma spesso gli utenti devono poter cercare rapidamente un elemento o ordinare i risultati. Un esempio comune è l’elenco degli ordini, dei prodotti, dei dipendenti o dei risultati di una ricerca.

In questo tutorial realizzeremo una tabella HTML per visualizzare un catalogo di libri. La struttura di base sarà completamente fruibile anche senza JavaScript; in seguito aggiungeremo funzionalità interattive come filtro testuale e ordinamento delle colonne. Questo approccio si chiama progressive enhancement: prima si costruisce un documento HTML corretto, poi si aggiungono comportamenti avanzati senza compromettere la struttura principale.

Il punto importante è non usare una tabella come semplice contenitore grafico. Una tabella deve rappresentare dati realmente tabellari, con intestazioni comprensibili e valori coerenti. L’interattività deve aiutare la consultazione, non sostituire la semantica HTML.

Codice completo

<h1>Catalogo libri</h1>

<label for="filtro-libro">Cerca un libro:</label>
<input
  type="search"
  id="filtro-libro"
  placeholder="Titolo, autore o categoria"
  aria-describedby="stato-filtro"
>

<p id="stato-filtro" aria-live="polite">
  Visualizzati 4 libri
</p>

<table id="catalogo">
  <caption>Libri disponibili nel catalogo</caption>

  <thead>
    <tr>
      <th scope="col" data-colonna="titolo">Titolo</th>
      <th scope="col" data-colonna="autore">Autore</th>
      <th scope="col" data-colonna="anno">Anno</th>
      <th scope="col" data-colonna="prezzo">Prezzo</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Il sentiero dei dati</td>
      <td>Luca Bianchi</td>
      <td data-valore="2022">2022</td>
      <td data-valore="24.90">24,90 €</td>
    </tr>
    <tr>
      <td>HTML in pratica</td>
      <td>Sara Conti</td>
      <td data-valore="2024">2024</td>
      <td data-valore="31.50">31,50 €</td>
    </tr>
    <tr>
      <td>Pensare il web</td>
      <td>Marco Riva</td>
      <td data-valore="2021">2021</td>
      <td data-valore="18.00">18,00 €</td>
    </tr>
    <tr>
      <td>Accessibilità digitale</td>
      <td>Elena Neri</td>
      <td data-valore="2023">2023</td>
      <td data-valore="27.00">27,00 €</td>
    </tr>
  </tbody>
</table>

<script>
  const input = document.querySelector("#filtro-libro");
  const tabella = document.querySelector("#catalogo");
  const corpo = tabella.querySelector("tbody");
  const messaggio = document.querySelector("#stato-filtro");
  const intestazioni = tabella.querySelectorAll("th[data-colonna]");

  function aggiornaRisultati() {
    const testo = input.value.toLowerCase().trim();
    const righe = corpo.querySelectorAll("tr");
    let visibili = 0;

    righe.forEach((riga) => {
      const contenuto = riga.textContent.toLowerCase();
      const corrisponde = contenuto.includes(testo);

      riga.hidden = !corrisponde;

      if (corrisponde) {
        visibili++;
      }
    });

    messaggio.textContent = `Visualizzati ${visibili} libri`;
  }

  function ordinaTabella(indice, crescente) {
    const righe = [...corpo.querySelectorAll("tr")];

    righe.sort((prima, seconda) => {
      const primaCella = prima.cells[indice];
      const secondaCella = seconda.cells[indice];

      const primoValore =
        primaCella.dataset.valore || primaCella.textContent.trim();
      const secondoValore =
        secondaCella.dataset.valore || secondaCella.textContent.trim();

      const confrontoNumerico =
        Number(primoValore) - Number(secondoValore);

      if (!Number.isNaN(confrontoNumerico) &&
          primoValore !== "" && secondoValore !== "") {
        return crescente ? confrontoNumerico : -confrontoNumerico;
      }

      return crescente
        ? primoValore.localeCompare(secondoValore, "it")
        : secondoValore.localeCompare(primoValore, "it");
    });

    righe.forEach((riga) => corpo.appendChild(riga));
  }

  input.addEventListener("input", aggiornaRisultati);

  intestazioni.forEach((intestazione, indice) => {
    let crescente = true;

    intestazione.addEventListener("click", () => {
      ordinaTabella(indice, crescente);
      crescente = !crescente;
    });
  });
</script>

Spiegazione

Una base HTML indipendente da JavaScript

La tabella contiene un elemento <caption> che descrive il suo contenuto. Le intestazioni sono inserite in <thead>, mentre i dati sono organizzati in <tbody>. L’attributo scope="col" indica che ogni elemento <th> identifica una colonna.

Il campo di ricerca è un normale elemento <input type="search">. Anche se lo script non fosse eseguito, l’utente vedrebbe comunque una tabella completa e leggibile. Questa è una caratteristica fondamentale: JavaScript migliora l’esperienza, ma non deve essere l’unico modo per ottenere i dati.

Filtrare senza modificare il contenuto

La funzione aggiornaRisultati() confronta il testo inserito con il contenuto completo di ogni riga. Le righe che non corrispondono vengono nascoste usando la proprietà hidden. In questo modo non è necessario ricostruire l’HTML della tabella a ogni pressione di un tasto.

Il testo dentro aria-live="polite" comunica il numero di risultati aggiornato alle tecnologie assistive, senza interrompere bruscamente la navigazione dell’utente.

Ordinare dati testuali e numerici

L’ordinamento di un testo e quello di un numero non sono equivalenti. Per esempio, un confronto alfabetico potrebbe collocare “100” prima di “20”, anche se numericamente il risultato è errato. Per evitare il problema vengono usati gli attributi personalizzati data-valore, che contengono il valore numerico reale di anno e prezzo.

Il testo visualizzato può quindi usare la formattazione italiana, come 24,90 €, mentre lo script ordina il valore 24.90. Separare il valore tecnico da quello mostrato è utile anche quando una cella contiene unità di misura, simboli o formati localizzati.

Best practice

  • Costruisci prima la tabella semantica: definisci correttamente intestazioni, righe e celle prima di aggiungere filtri o ordinamenti.
  • Non affidarti solo al colore: se una colonna è ordinata, rendi lo stato comprensibile anche tramite testo, icone con descrizione o attributi appropriati.
  • Usa valori coerenti: nella stessa colonna non mescolare numeri, testo libero e valori privi di significato senza una regola precisa.
  • Conserva i dati originali: evita di modificare definitivamente il contenuto delle celle durante il filtro. Nascondere o riordinare le righe è più sicuro e reversibile.
  • Comunica i risultati: un messaggio come “Visualizzati 2 libri” aiuta a capire immediatamente l’effetto del filtro.
  • Considera le tabelle molto grandi: per migliaia di righe è preferibile filtrare i dati sul server o usare paginazione, invece di caricare tutto nel browser.
  • Prevedi un’alternativa al clic: se l’ordinamento è una funzione importante, valuta controlli da tastiera o pulsanti espliciti invece di rendere cliccabile solo il testo dell’intestazione.
  • Non usare la tabella per il layout: griglie grafiche, schede e colonne di una pagina devono essere realizzate con CSS, non con righe e celle HTML.

Riepilogo

Una tabella interattiva ben progettata nasce da una struttura HTML corretta, non da uno script. <caption>, <thead>, <tbody>, <th> e <td> descrivono il contenuto in modo semantico. Il filtro può essere implementato nascondendo le righe non pertinenti, mentre l’ordinamento deve distinguere i valori numerici da quelli testuali.

L’uso di data-valore permette di mostrare dati formattati in modo leggibile senza perdere la possibilità di confrontarli correttamente. Infine, messaggi dinamici, contenuti comprensibili e funzionamento di base indipendente da JavaScript rendono la soluzione più robusta e mantenibile.

Approfondisci con risorse ufficiali

SHARE