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:
- scrivere la struttura HTML di partenza, con un filtro per aula (elenco a discesa) e per studente (testo libero, "contiene");
- 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; - aggiornare la tabella a ogni modifica del filtro e mostrare "N prenotazioni su M";
- 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
<!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>- Le
optiondell'aula, tranne "Tutte" (valore""), le crea JavaScript dall'array delle aule: se arrivano dal server non si possono scrivere a mano. - La
labelche contiene il campo lo collega senza bisogno difor. - Il form dei filtri non va inviato: lo si "spegne" con
preventDefault(altrimenti Invio nel campo di ricerca ricarica la pagina). aria-live="polite"fa leggere ai lettori di schermo il conteggio quando cambia.- Lo script è in fondo al
body(poteva stare in testa condefer): gli elementi esistono già quando il codice li cerca (Architettura delle applicazioni web - browser, server web e livelliUn'applicazione web è distribuita su più livelli (tier): presentazione (browser: HTML, CSS, JavaScript), logica applicativa (server web e application server) e dati (DBMS). Il client invia richieste HTTP, il server risponde; HTTP è senza stato, quindi lo stato dell'utente viaggia in cookie e sessioni. Il web server serve file statici, l'application server (container, per esempio Tomcat per le servlet Java) esegue codice che genera risposte dinamiche; spesso il web server fa da reverse proxy davanti all'application server. Il browser trasforma i byte della risposta in una pagina: decodifica, tokenizzazione e costruzione del DOM, CSS che diventa CSSOM, render tree, layout (reflow), paint e composizione; un script senza defer o async blocca il parsing, il CSS blocca il rendering. Origine = schema + host + porta; la same-origin policy limita ciò che uno script può leggere da altre origini. Web 1.0: pagine generate dal server, ogni azione ricarica la pagina; Web 2.0: AJAX, contenuti degli utenti, servizi REST; Web 3.0: dati con significato esplicito (RDF, linked data).Architettura delle applicazioni web - browser, server web e livelli →, blocco del parsing).
Parte 2 - 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:
filtra(righe, aulaId, testo)è la logica, separata dal DOM.testo.trim().toLowerCase()normalizza la ricerca;aulaId === ""significa "tutte" (valore della primaoption); altrimenti si confrontap.aula_idcon il numero:selAula.valueè una stringa,aula_idun numero, e"1" === 1è falso, per questoNumber(aulaId).includes("")è sempre vero: un testo vuoto non filtra. Le due condizioni sono in&&: la riga resta se soddisfa entrambe.nomeAulacerca nell'array delle aule l'elemento con quell'id(findrestituisceundefinedse non c'è, quindi il controllo). Corrisponde a un join in SQL fraprenotazioneeaula(SQL - interrogazioni, join e aggregazioniUn'interrogazione SQL ha almeno SELECT e FROM; le altre clausole (WHERE, GROUP BY, HAVING, ORDER BY) sono opzionali e HAVING richiede GROUP BY. Ordine logico di valutazione: FROM, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY. SELECT corrisponde a π (ma senza DISTINCT non elimina i duplicati), WHERE a σ, gli alias ad AS/ρ, i join a ⋈ (INNER, LEFT, RIGHT, FULL). WHERE seleziona le righe prima del raggruppamento, HAVING seleziona i gruppi dopo. Le funzioni COUNT, SUM, AVG, MIN, MAX ignorano i NULL (COUNT(*) conta le righe, DISTINCT toglie i duplicati dentro la funzione); con un aggregato o con GROUP BY nella SELECT possono comparire solo colonne di raggruppamento o aggregati. LIKE usa % (qualsiasi sequenza) e _ (un carattere). I confronti con NULL non sono veri: serve IS NULL. Un filtro sulla tabella di destra di un LEFT JOIN va nella clausola ON, non in WHERE, altrimenti il join diventa interno. Con interi COUNT(a)/COUNT(b) è una divisione intera: serve CAST.SQL - interrogazioni, join e aggregazioni →); lo si poteva anche fare sul server e inviare il nome direttamente.creaCellacrea un elemento (thotd) e ne imposta il testo contextContent, non coninnerHTML: un cognome come<b>Verdi</b> & Coviene mostrato come testo (provato: il DOM contiene<b>Verdi</b> & Co, nessun elemento in più). ConinnerHTMLun nome scritto da un utente malintenzionato potrebbe eseguire codice (XSS).creaTabellacostruiscetable,thead,tbodycon i metodi del DOM per le tabelle (createTHead,insertRow,createTBody).th.scope = "col"rende la tabella accessibile (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 →).tr.dataset.id = p.idmettedata-id="..."sulla riga: servirà per eventi (per esempio un pulsante "elimina").tr.append(...)accetta più nodi.disegnafiltra, costruisce una nuova tabella e sostituisce il contenuto del contenitore conreplaceChildren, poi aggiorna il conteggio. Si ricostruisce tutto a ogni modifica: semplice e sufficiente per poche righe (con migliaia di righe si aggiornerebbero solo le differenze).- I gestori:
changesulselect(scatta alla scelta),inputsul campo di testo (scatta a ogni tasto); si passadisegnasenza parentesi. Ilsubmitdel form dei filtri è annullato conpreventDefault. - L'ultima riga,
disegna(), mostra la tabella iniziale.
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 (
ROSSItrova Rossi). - I due filtri si combinano (
aulaetestoinsieme: Lab Info +rosdà solo la prenotazione 4; Aula B2 +rosdà 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:
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; });fetchrestituisce una promise: le due richieste partono insieme conPromise.all, che si risolve quando sono arrivate entrambe (con un'attesa di 100 ms ciascuna, in parallelo, il totale è circa 100 ms e non 200).fetchnon rifiuta per un 404 o un 500: serve controllareok(quirAule.ok).await rAule.json()legge il corpo e lo trasforma da JSON in un array di oggetti con le stesse chiavi usate nel codice (aula_id,ora_inizio, ...). Ecco perché i nomi nell'API sono quelli delle colonne.- Nulla si disegna finché i dati non sono arrivati (
disegna()si chiama nelthen); in caso di errore il messaggio appare nel conteggio. - Provato con un
fetchsimulato innode: con due risposteokrestituisce tre aule e quattro prenotazioni; se una risposta è 500, la funzione lanciaerrore HTTP 200/500e ilcatchmostra il messaggio. - La pagina e le API devono avere la stessa origine (stesso schema, host e porta: Architettura delle applicazioni web - browser, server web e livelliUn'applicazione web è distribuita su più livelli (tier): presentazione (browser: HTML, CSS, JavaScript), logica applicativa (server web e application server) e dati (DBMS). Il client invia richieste HTTP, il server risponde; HTTP è senza stato, quindi lo stato dell'utente viaggia in cookie e sessioni. Il web server serve file statici, l'application server (container, per esempio Tomcat per le servlet Java) esegue codice che genera risposte dinamiche; spesso il web server fa da reverse proxy davanti all'application server. Il browser trasforma i byte della risposta in una pagina: decodifica, tokenizzazione e costruzione del DOM, CSS che diventa CSSOM, render tree, layout (reflow), paint e composizione; un script senza defer o async blocca il parsing, il CSS blocca il rendering. Origine = schema + host + porta; la same-origin policy limita ciò che uno script può leggere da altre origini. Web 1.0: pagine generate dal server, ogni azione ricarica la pagina; Web 2.0: AJAX, contenuti degli utenti, servizi REST; Web 3.0: dati con significato esplicito (RDF, linked data).Architettura delle applicazioni web - browser, server web e livelli →), altrimenti il browser blocca la lettura delle risposte; le pagine non vanno aperte da
file://. - Se il filtro per aula fosse molto pesante, si potrebbe chiedere al server già filtrato (
/api/prenotazioni?aula=1): una scelta fra lavoro del client e del server.
Errori tipici
- Confrontare
p.aula_idconselAula.valuesenza convertire:1 === "1"è falso e il filtro non trova nulla. - Usare
innerHTMLcon i dati ricevuti: XSS. - Registrare
addEventListener("input", disegna())con le parentesi: la funzione parte subito e si registraundefined. - Dimenticare il
preventDefaultdel form dei filtri. - Dimenticare
awaitdavanti afetcho ajson(): 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.
filtra(righe, aulaId, testo):aulaId === "" || p.aula_id === Number(aulaId)ep.studente.toLowerCase().includes(testo.trim().toLowerCase()).selAula.valueè una stringa: serveNumber.nomeAula(aule, id)confind: è il join conaula.
DOM.
creaCella(tag, testo)concreateElementetextContent: maiinnerHTMLcon 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:
changesulselect,inputsul testo;disegnasenza 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.
Promise.all([fetch("/api/aule"), fetch("/api/prenotazioni")]), controllook(il 404 non rifiuta la promise),await r.json().- Disegnare nel
then, errori nelcatch. - Stessa origine, mai da
file://(Architettura delle applicazioni web - browser, server web e livelliUn'applicazione web è distribuita su più livelli (tier): presentazione (browser: HTML, CSS, JavaScript), logica applicativa (server web e application server) e dati (DBMS). Il client invia richieste HTTP, il server risponde; HTTP è senza stato, quindi lo stato dell'utente viaggia in cookie e sessioni. Il web server serve file statici, l'application server (container, per esempio Tomcat per le servlet Java) esegue codice che genera risposte dinamiche; spesso il web server fa da reverse proxy davanti all'application server. Il browser trasforma i byte della risposta in una pagina: decodifica, tokenizzazione e costruzione del DOM, CSS che diventa CSSOM, render tree, layout (reflow), paint e composizione; un script senza defer o async blocca il parsing, il CSS blocca il rendering. Origine = schema + host + porta; la same-origin policy limita ciò che uno script può leggere da altre origini. Web 1.0: pagine generate dal server, ogni azione ricarica la pagina; Web 2.0: AJAX, contenuti degli utenti, servizi REST; Web 3.0: dati con significato esplicito (RDF, linked data).Architettura delle applicazioni web - browser, server web e livelli →).
Errori tipici
- Confronto stringa/numero senza
Number. innerHTMLcon dati del server.disegna()con le parentesi nel gestore.- Dimenticare
preventDefault,awaitook. - Disegnare prima dell'arrivo dei dati. Struttura del codice.
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 conaula_id. includes("")è sempre vero: testo vuoto significa nessun filtro per studente.- Cognomi come
<b>Verdi</b> & Corestano testo contextContent; coninnerHTMLsarebbero HTML. - Il nome dell'aula si ricava con
findsull'array delle aule (join conaula). - Con
fetch,Promise.allper aule e prenotazioni,okcontrollato, disegno dentro ilthen.