Salta al contenuto
Note per Studenti Esercizio - Tabella delle prenotazioni costruita con il DOM e filtrata (applicazione web)

Esercizio - Tabella delle prenotazioni costruita con il DOM e filtrata (applicazione web)

In questa pagina 4

Testo. La pagina prenotazioni.html dell'applicazione di prenotazione delle aule riceve dal server (o, per ora, da un array JavaScript nello stesso formato del JSON dell'API) l'elenco delle aule e delle prenotazioni. Si chiede di:

  1. scrivere la struttura HTML di partenza, con un filtro per aula (elenco a discesa) e per studente (testo libero, "contiene");
  2. costruire la tabella con il DOM (senza innerHTML), con colonne aula, giorno, dalle, alle, studente; la colonna dell'aula mostra il nome (Aula A1), non l'aula_id;
  3. aggiornare la tabella a ogni modifica del filtro e mostrare "N prenotazioni su M";
  4. sostituire l'array con i dati letti dal server con fetch.

Teoria: JavaScript - linguaggio, DOM ed eventiJavaScript (standard ECMAScript, Ecma-262) è il linguaggio dei browser: tipizzato dinamicamente, con sette tipi primitivi (number, string, boolean, undefined, null, bigint, symbol) e gli oggetti (anche array e funzioni); typeof null vale "object" per un errore storico; == converte i tipi, === no (si usa sempre ===); valori falsy: false, 0, 0n, "", null, undefined, NaN. let e const hanno visibilità di blocco e una zona morta prima della dichiarazione, var visibilità di funzione e hoisting; const impedisce di riassegnare il nome, non di modificare l'oggetto. Funzioni: dichiarazione (hoisted), espressione, arrow function (this lessicale), parametri predefiniti, rest e spread, destructuring. Array: map, filter, reduce, find, some, every, sort (senza comparatore ordina come stringhe). Closure: una funzione ricorda le variabili dell'ambiente in cui è nata. Eventi: addEventListener; propagazione in tre fasi (cattura, target, bolla), stopPropagation, preventDefault, delegazione con event.target e closest. DOM: albero di nodi; querySelector, createElement, append, replaceChildren, textContent (sicuro) contro innerHTML (rischio di XSS), classList, dataset. Moduli ES (type="module", import e export) sono differiti e in modalità strict. Event loop: un solo thread, call stack, coda dei task (setTimeout, eventi) e coda dei microtask (promise, await) svuotata dopo ogni task: l'esecuzione asincrona (fetch, async e await, Promise.all) è alla base di AJAX e il codice lungo blocca la pagina.JavaScript - linguaggio, DOM ed eventi → (array, DOM, eventi, fetch), HTML5 - struttura del documento, elementi semantici e formUn documento HTML5 ha la forma doctype (), html con lang, head (meta charset UTF-8, viewport, title, link, script) e body. Il parser HTML5 non dà mai errore: corregge (aggiunge il tbody, chiude i p). Gli elementi semantici (header, nav, main, section, article, aside, footer, figure, time) descrivono il ruolo del contenuto e danno ai lettori di schermo i punti di riferimento; div e span sono neutri. Link: a href (assoluto, relativo, #frammento, mailto), rel e target; immagini con alt obbligatorio. Tabelle: table, caption, thead, tbody, th con scope, td, colspan e rowspan, solo per dati. Form: action, method (GET mette i campi nella query string, per letture idempotenti e ricercabili; POST li mette nel corpo, per modificare lo stato), enctype (application/x-www-form-urlencoded predefinito, multipart/form-data per i file); si invia solo ciò che ha name; label for collega etichetta e campo; tipi di input (text, email, number, date, time, radio, checkbox, hidden...); validazione nativa con required, minlength, maxlength, min, max, step, pattern e type, aggirabile e quindi da ripetere sul server; novalidate la spegne. Accessibilità: lang, alt, label, gerarchia dei titoli, navigazione da tastiera, ARIA solo quando l'HTML nativo non basta.HTML5 - struttura del documento, elementi semantici e form → (tabelle accessibili) e Web service, XML, JSON, SOAP e RESTUn web service e' un servizio software accessibile via rete da altri programmi, con messaggi in formato standard su HTTP; i formati di dati sono XML (marcatori annidati, attributi, namespace, validazione con XSD) e JSON (RFC 8259: oggetti, array, stringhe, numeri, true, false, null; piu' compatto e diretto per i linguaggi di programmazione); SOAP e' un protocollo di messaggi XML (Envelope con Header facoltativo e Body, Fault per gli errori) inviati di solito con una POST HTTP e descritti da un file WSDL; REST e' uno stile architetturale (Fielding) in cui il servizio espone risorse identificate da URI e le manipola con i metodi HTTP: GET legge, POST crea, PUT sostituisce, PATCH modifica, DELETE elimina, con codici di stato significativi (200, 201 con Location, 204, 400, 404, 409), senza stato sul server, con cache e interfaccia uniforme.Web service, XML, JSON, SOAP e REST → (il JSON dell'API). Il codice è stato provato in un browser (Edge) e la logica con node.

Schema dei dati (come nel database e nel JSON dell'API):

aula.id nome posti
1 Aula A1 80
2 Aula B2 45
3 Lab Info 30
prenotazione.id aula_id giorno ora_inizio ora_fine studente
1 1 2026-10-19 09:00 11:00 Rossi
2 1 2026-10-19 14:00 16:00 Bianchi
3 2 2026-10-20 10:00 12:00 Verdi
4 3 2026-10-21 09:00 13:00 Rossi

Parte 1 - HTML di partenza

html
<!DOCTYPE html>
<html lang="it">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Prenotazioni delle aule</title>
</head>
<body>
  <h1>Prenotazioni delle aule</h1>

  <form id="filtri">
    <label>Aula
      <select id="f-aula">
        <option value="">Tutte</option>
      </select>
    </label>
    <label>Studente
      <input type="search" id="f-studente" placeholder="parte del cognome">
    </label>
  </form>

  <p id="conteggio" aria-live="polite"></p>
  <div id="tabella"></div>

  <script src="prenotazioni.js"></script>
</body>
</html>

Parte 2 - JavaScript

javascript
const aule = [
  { id: 1, nome: "Aula A1", posti: 80 },
  { id: 2, nome: "Aula B2", posti: 45 },
  { id: 3, nome: "Lab Info", posti: 30 }
];

const prenotazioni = [
  { id: 1, aula_id: 1, giorno: "2026-10-19", ora_inizio: "09:00", ora_fine: "11:00", studente: "Rossi" },
  { id: 2, aula_id: 1, giorno: "2026-10-19", ora_inizio: "14:00", ora_fine: "16:00", studente: "Bianchi" },
  { id: 3, aula_id: 2, giorno: "2026-10-20", ora_inizio: "10:00", ora_fine: "12:00", studente: "Verdi" },
  { id: 4, aula_id: 3, giorno: "2026-10-21", ora_inizio: "09:00", ora_fine: "13:00", studente: "Rossi" }
];

// Logica pura: nessun DOM, si prova con node.
function filtra(righe, aulaId, testo) {
  const t = testo.trim().toLowerCase();
  return righe.filter(p =>
    (aulaId === "" || p.aula_id === Number(aulaId)) &&
    p.studente.toLowerCase().includes(t));
}

function nomeAula(aule, id) {
  const a = aule.find(x => x.id === id);
  return a ? a.nome : "?";
}

function creaCella(tag, testo) {
  const c = document.createElement(tag);
  c.textContent = testo;            // testo, non HTML: nessuna injection
  return c;
}

function creaTabella(righe, aule) {
  const tabella = document.createElement("table");
  const thead = tabella.createTHead();
  const intest = thead.insertRow();
  for (const titolo of ["Aula", "Giorno", "Dalle", "Alle", "Studente"]) {
    const th = creaCella("th", titolo);
    th.scope = "col";
    intest.append(th);
  }
  const tbody = tabella.createTBody();
  for (const p of righe) {
    const tr = tbody.insertRow();
    tr.dataset.id = p.id;
    tr.append(
      creaCella("td", nomeAula(aule, p.aula_id)),
      creaCella("td", p.giorno),
      creaCella("td", p.ora_inizio),
      creaCella("td", p.ora_fine),
      creaCella("td", p.studente));
  }
  return tabella;
}

const selAula = document.getElementById("f-aula");
const inpStudente = document.getElementById("f-studente");
const contenitore = document.getElementById("tabella");
const conteggio = document.getElementById("conteggio");

function disegna() {
  const righe = filtra(prenotazioni, selAula.value, inpStudente.value);
  contenitore.replaceChildren(creaTabella(righe, aule));
  conteggio.textContent = righe.length + " prenotazioni su " + prenotazioni.length;
}

for (const a of aule) {
  const o = document.createElement("option");
  o.value = a.id;
  o.textContent = a.nome;
  selAula.append(o);
}
selAula.addEventListener("change", disegna);
inpStudente.addEventListener("input", disegna);
document.getElementById("filtri").addEventListener("submit", e => e.preventDefault());
disegna();

Spiegazione, in ordine:

Parte 3 - Risultati verificati

Caricando la pagina in un browser, il DOM contiene la tabella con le quattro righe e il conteggio "4 prenotazioni su 4": <tr data-id="1"><td>Aula A1</td><td>2026-10-19</td><td>09:00</td><td>11:00</td><td>Rossi</td></tr> e così via (nomi delle aule, non id). Simulando le interazioni (scelta dell'aula, scrittura nel campo, eventi change e input):

aula testo righe (data-id) conteggio
Aula A1 (vuoto) 1, 2 2 prenotazioni su 4
Tutte ROSSI 1, 4 2 prenotazioni su 4
Lab Info ROSSI 4 1 prenotazioni su 4
Lab Info x nessuna 0 prenotazioni su 4
  • Il filtro ignora maiuscole e spazi iniziali e finali (ROSSI trova Rossi).
  • I due filtri si combinano (aula e testo insieme: Lab Info + ros dà solo la prenotazione 4; Aula B2 + ros dà nessuna riga).
  • Il conteggio "1 prenotazioni" è sgrammaticato: per una versione curata si sceglie la parola secondo il numero (n === 1 ? "prenotazione" : "prenotazioni").

Parte 4 - Dati dal server con fetch

Con l'API REST dell'applicazione (/api/aule e /api/prenotazioni, con le risposte in JSON) gli array non sono più scritti nel codice. Le due variabili diventano let, vuote, e si riempiono all'avvio:

javascript
let aule = [];
let prenotazioni = [];

async function carica() {
  const [rAule, rPren] = await Promise.all([fetch("/api/aule"), fetch("/api/prenotazioni")]);
  if (!rAule.ok || !rPren.ok) throw new Error("errore HTTP " + rAule.status + "/" + rPren.status);
  return { aule: await rAule.json(), prenotazioni: await rPren.json() };
}

carica()
  .then(d => {
    aule = d.aule;
    prenotazioni = d.prenotazioni;
    for (const a of aule) { /* crea le option dell'elenco, come sopra */ }
    disegna();
  })
  .catch(e => { conteggio.textContent = "Errore nel caricamento: " + e.message; });

Errori tipici

  • Confrontare p.aula_id con selAula.value senza convertire: 1 === "1" è falso e il filtro non trova nulla.
  • Usare innerHTML con i dati ricevuti: XSS.
  • Registrare addEventListener("input", disegna()) con le parentesi: la funzione parte subito e si registra undefined.
  • Dimenticare il preventDefault del form dei filtri.
  • Dimenticare await davanti a fetch o a json(): si lavora su una promise.
  • Non controllare risposta.ok.
  • Disegnare prima che i dati arrivino (la tabella resta vuota).
  • Filtrare con ==, o senza normalizzare le maiuscole.

Versione ripasso

HTML. form#filtri con select#f-aula (prima option value="" = Tutte) e input type="search"#f-studente; p#conteggio con aria-live="polite"; div#tabella; script in fondo o con defer. Il form dei filtri si annulla con preventDefault.

Dati (Web service, XML, JSON, SOAP e RESTUn web service e' un servizio software accessibile via rete da altri programmi, con messaggi in formato standard su HTTP; i formati di dati sono XML (marcatori annidati, attributi, namespace, validazione con XSD) e JSON (RFC 8259: oggetti, array, stringhe, numeri, true, false, null; piu' compatto e diretto per i linguaggi di programmazione); SOAP e' un protocollo di messaggi XML (Envelope con Header facoltativo e Body, Fault per gli errori) inviati di solito con una POST HTTP e descritti da un file WSDL; REST e' uno stile architetturale (Fielding) in cui il servizio espone risorse identificate da URI e le manipola con i metodi HTTP: GET legge, POST crea, PUT sostituisce, PATCH modifica, DELETE elimina, con codici di stato significativi (200, 201 con Location, 204, 400, 404, 409), senza stato sul server, con cache e interfaccia uniforme.Web service, XML, JSON, SOAP e REST →): aule {id, nome, posti} e prenotazioni {id, aula_id, giorno, ora_inizio, ora_fine, studente}: stessi nomi delle colonne.

Logica pura (JavaScript - linguaggio, DOM ed eventiJavaScript (standard ECMAScript, Ecma-262) è il linguaggio dei browser: tipizzato dinamicamente, con sette tipi primitivi (number, string, boolean, undefined, null, bigint, symbol) e gli oggetti (anche array e funzioni); typeof null vale "object" per un errore storico; == converte i tipi, === no (si usa sempre ===); valori falsy: false, 0, 0n, "", null, undefined, NaN. let e const hanno visibilità di blocco e una zona morta prima della dichiarazione, var visibilità di funzione e hoisting; const impedisce di riassegnare il nome, non di modificare l'oggetto. Funzioni: dichiarazione (hoisted), espressione, arrow function (this lessicale), parametri predefiniti, rest e spread, destructuring. Array: map, filter, reduce, find, some, every, sort (senza comparatore ordina come stringhe). Closure: una funzione ricorda le variabili dell'ambiente in cui è nata. Eventi: addEventListener; propagazione in tre fasi (cattura, target, bolla), stopPropagation, preventDefault, delegazione con event.target e closest. DOM: albero di nodi; querySelector, createElement, append, replaceChildren, textContent (sicuro) contro innerHTML (rischio di XSS), classList, dataset. Moduli ES (type="module", import e export) sono differiti e in modalità strict. Event loop: un solo thread, call stack, coda dei task (setTimeout, eventi) e coda dei microtask (promise, await) svuotata dopo ogni task: l'esecuzione asincrona (fetch, async e await, Promise.all) è alla base di AJAX e il codice lungo blocca la pagina.JavaScript - linguaggio, DOM ed eventi →):

  • filtra(righe, aulaId, testo): aulaId === "" || p.aula_id === Number(aulaId) e p.studente.toLowerCase().includes(testo.trim().toLowerCase()).
  • selAula.value è una stringa: serve Number.
  • nomeAula(aule, id) con find: è il join con aula.

DOM.

  • creaCella(tag, testo) con createElement e textContent: mai innerHTML con dati esterni (XSS).
  • creaTabella: createTHead, insertRow, createTBody, th.scope = "col", tr.dataset.id = p.id.
  • disegna(): filtra, contenitore.replaceChildren(creaTabella(...)), aggiorna "N prenotazioni su M".
  • Eventi: change sul select, input sul testo; disegna senza parentesi.

Risultati. Aula A1: righe 1, 2; testo ROSSI: 1, 4; Lab Info e ros: 4; Aula B2 e ros: nessuna. I filtri si combinano; maiuscole e spazi sono ignorati.

Server.

Errori tipici

  • Confronto stringa/numero senza Number.
  • innerHTML con dati del server.
  • disegna() con le parentesi nel gestore.
  • Dimenticare preventDefault, await o ok.
  • Disegnare prima dell'arrivo dei dati. Struttura del codice.
javascript
function filtra(righe, aulaId, testo) {
  const t = testo.trim().toLowerCase();
  return righe.filter(p => (aulaId === "" || p.aula_id === Number(aulaId)) && p.studente.toLowerCase().includes(t));
}
function disegna() {
  const righe = filtra(prenotazioni, selAula.value, inpStudente.value);
  contenitore.replaceChildren(creaTabella(righe, aule));
  conteggio.textContent = righe.length + " prenotazioni su " + prenotazioni.length;
}
selAula.addEventListener("change", disegna);
inpStudente.addEventListener("input", disegna);

Risultati provati nel browser.

aula testo righe conteggio
Aula A1 vuoto 1, 2 2 su 4
Tutte ROSSI 1, 4 2 su 4
Lab Info ROSSI 4 1 su 4
Lab Info x nessuna 0 su 4

Punti da ricordare.

  • Il valore di un select è una stringa: Number(aulaId) prima del confronto con aula_id.
  • includes("") è sempre vero: testo vuoto significa nessun filtro per studente.
  • Cognomi come <b>Verdi</b> & Co restano testo con textContent; con innerHTML sarebbero HTML.
  • Il nome dell'aula si ricava con find sull'array delle aule (join con aula).
  • Con fetch, Promise.all per aule e prenotazioni, ok controllato, disegno dentro il then.

Teoria collegata