Progetto Finale

Bentornato.
Questa è l’ultima lezione del corso JavaScript.
Sì.
Il final boss.
Ma non preoccuparti.
Questo boss non sputa fuoco.
Più o meno.
Nelle lezioni precedenti hai imparato molte idee importanti di JavaScript:
- variabili;
- condizioni;
- funzioni;
- array;
- oggetti;
- DOM;
- eventi;
- form;
- validazione;
- local storage;
- fetch.
Oggi le mettiamo insieme.
Costruiremo un piccolo task manager.
Non una grande app.
Non una startup.
Non un impero della produttività con dashboard motivazionale e sette piani di abbonamento.
Solo un progetto pulito per principianti.
L’app farà questo:
- caricherà attività iniziali da un file JSON;
- mostrerà le attività nella pagina;
- permetterà all’utente di aggiungere una nuova attività;
- validerà input vuoti;
- segnerà attività come completate;
- eliminerà attività;
- salverà attività in local storage;
- ricorderà le attività dopo il refresh.
Questo è un vero progetto frontend.
Piccolo.
Utile.
Molto JavaScript.
Cosa Costruirai
Costruirai un task manager.
L’utente potrà:
- vedere una lista di attività;
- aggiungere nuove attività;
- segnare attività come completate;
- eliminare attività;
- cancellare tutte le attività.
L’app salverà anche le attività in local storage.
Quindi, se l’utente aggiorna la pagina, le attività resteranno lì.
Questo significa che combineremo:
- struttura HTML;
- stile CSS;
- dati JavaScript;
- rendering nel DOM;
- event listener;
- gestione form;
- validazione;
- local storage;
- fetch;
- JSON.
In pratica, facciamo indossare a JavaScript tutti i suoi vestiti insieme.
Elegante?
Forse.
Educativo?
Assolutamente.
Crea il Progetto
Crea una cartella per questa lezione:
mkdir javascript-lesson12
cd javascript-lesson12
touch index.html
touch script.js
touch tasks.json
Il progetto dovrebbe essere così:
javascript-lesson12/
index.html
script.js
tasks.json
Importante:
Dato che useremo fetch(), avvia il progetto con un server locale.
Per esempio, con Caddy:
caddy file-server --listen :8080
Poi apri:
http://localhost:8080
Non aprire semplicemente index.html con doppio click.
Il browser potrebbe non permettere a fetch() di caricare file JSON locali da file://.
I browser hanno regole.
Molte regole.
Alcune utili.
Alcune sembrano scritte da un drago con la burocrazia.
Crea i Dati Iniziali
Apri tasks.json e aggiungi:
[
{
"id": 1,
"title": "Review JavaScript variables",
"completed": false
},
{
"id": 2,
"title": "Practice DOM manipulation",
"completed": false
},
{
"id": 3,
"title": "Build the final project",
"completed": false
}
]
Questo è un array di oggetti.
Ogni attività ha:
id;title;completed.
Questo è molto comune nei progetti reali.
Attività.
Prodotti.
Utenti.
Post del blog.
Ordini.
Molto spesso i dati sono un array di oggetti.
JavaScript guarda gli array di oggetti e dice:
Ah sì, casa.
Scrivi l'HTML
Apri index.html e aggiungi:
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Progetto Finale</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 60px auto;
padding: 0 24px;
background-color: #f3f4f6;
color: #111827;
}
.card {
background-color: white;
padding: 24px;
border: 2px solid #e5e7eb;
border-radius: 18px;
}
h1 {
font-size: 42px;
margin-bottom: 8px;
}
p {
font-size: 18px;
line-height: 1.6;
}
form {
display: flex;
gap: 10px;
margin-top: 20px;
}
input {
flex: 1;
padding: 12px;
border: 2px solid #d1d5db;
border-radius: 12px;
font-size: 18px;
}
button {
background-color: #2563eb;
color: white;
border: none;
padding: 12px 18px;
border-radius: 999px;
font-weight: 700;
cursor: pointer;
}
button:hover {
background-color: #1d4ed8;
}
.secondary {
background-color: #6b7280;
}
.secondary:hover {
background-color: #4b5563;
}
.danger {
background-color: #dc2626;
}
.danger:hover {
background-color: #b91c1c;
}
.message {
margin-top: 18px;
font-weight: 700;
}
.task-list {
list-style: none;
padding: 0;
margin-top: 22px;
display: grid;
gap: 12px;
}
.task {
display: flex;
justify-content: space-between;
gap: 12px;
align-items: center;
padding: 16px;
border: 2px solid #e5e7eb;
border-radius: 14px;
background-color: #f9fafb;
}
.task-title {
font-size: 20px;
font-weight: 700;
}
.completed .task-title {
text-decoration: line-through;
color: #6b7280;
}
.actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
@media (max-width: 600px) {
form {
flex-direction: column;
}
.task {
align-items: flex-start;
flex-direction: column;
}
}
</style>
</head>
<body>
<h1>Progetto Finale</h1>
<div class="card">
<p>
Aggiungi attività, completale, eliminale e aggiorna la pagina.
Le tue attività resteranno salvate nel browser.
</p>
<form id="taskForm">
<input id="taskInput" type="text" placeholder="Scrivi una nuova attività">
<button type="submit">Aggiungi</button>
</form>
<p id="message" class="message">Caricamento attività...</p>
<ul id="taskList" class="task-list"></ul>
<button id="clearButton" class="danger">Cancella Tutte le Attività</button>
</div>
<script src="script.js"></script>
</body>
</html>
Questo HTML ci dà:
- un form;
- un input;
- una lista attività;
- un’area messaggio;
- un pulsante cancella.
Il CSS lo fa sembrare una piccola app reale.
Non una pagina grigia dal museo dell’internet antico.
Bene.
Ora abbiamo standard.
Più o meno.
Pianifica il JavaScript
Prima di scrivere codice, capiamo cosa deve fare l’app.
Dobbiamo:
- selezionare elementi HTML;
- salvare attività in un array;
- caricare attività da local storage;
- se non ci sono attività salvate, caricare attività iniziali da
tasks.json; - renderizzare attività nella pagina;
- aggiungere una nuova attività dal form;
- validare input vuoto;
- segnare un’attività come completata;
- eliminare un’attività;
- cancellare tutte le attività;
- salvare modifiche in local storage.
Sembra tanto.
Ma sono solo piccoli pezzi.
I progetti JavaScript sono spesso così.
Una grande cosa spaventosa diventa tante piccole cose normali.
Come pulire una stanza.
Terribile se pensi a tutta la stanza.
Possibile se inizi da un calzino.
Seleziona gli Elementi
Apri script.js e aggiungi:
const taskForm = document.getElementById("taskForm");
const taskInput = document.getElementById("taskInput");
const taskListElement = document.getElementById("taskList");
const messageElement = document.getElementById("message");
const clearButton = document.getElementById("clearButton");
Ora JavaScript può lavorare con la pagina.
Queste variabili collegano JavaScript al DOM.
Il DOM è il ponte.
Senza questo ponte, JavaScript parla da solo in un angolo.
A volte utile.
Ma non oggi.
Crea l'Array delle Attività
Aggiungi questo:
let tasks = [];
Questo array salverà tutte le attività mentre l’app è in esecuzione.
Ogni attività sarà un oggetto così:
{
id: 1,
title: "Learn JavaScript",
completed: false
}
Quindi abbiamo:
- un array per la lista;
- oggetti per le singole attività.
Array e oggetti insieme.
Una squadra classica.
Come caffè e debugging.
Salva Attività in Local Storage
Aggiungi questa funzione:
function saveTasks() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
Questa salva l’array tasks in local storage.
Dato che local storage salva stringhe, usiamo:
JSON.stringify(tasks)
Questo converte l’array in una stringa JSON.
Senza JSON.stringify, JavaScript non salverà correttamente l’array.
Diventerà strano.
E JavaScript è già abbastanza strano senza aiuto.
Carica Attività da Local Storage
Aggiungi questa funzione:
function loadTasksFromStorage() {
const savedTasks = localStorage.getItem("tasks");
if (savedTasks === null) {
return false;
}
tasks = JSON.parse(savedTasks);
return true;
}
Questa funzione controlla se esistono già attività in local storage.
Se non ci sono attività salvate, ritorna false.
Se ci sono attività salvate, le carica dentro l’array tasks e ritorna true.
Questo ci aiuta a decidere:
Usa attività salvate se esistono.
Altrimenti carica attività iniziali dal JSON.
Molto organizzato.
Sospettosamente professionale.
Carica Attività Iniziali con Fetch
Aggiungi questa funzione:
async function loadDefaultTasks() {
try {
const response = await fetch("tasks.json");
if (!response.ok) {
throw new Error("Impossibile caricare le attività iniziali.");
}
tasks = await response.json();
saveTasks();
} catch (error) {
messageElement.textContent = "Impossibile caricare le attività iniziali.";
console.log(error);
}
}
Questa funzione carica attività da tasks.json.
Usa:
fetch;await;response.ok;response.json;try;catch.
Sono molte lezioni precedenti dentro una funzione.
Guarda un po’.
Stai usando conoscenza.
Pericoloso.
Ma bellissimo.
Renderizza le Attività
Ora dobbiamo mostrare le attività nella pagina.
Aggiungi questa funzione:
function renderTasks() {
taskListElement.innerHTML = "";
if (tasks.length === 0) {
messageElement.textContent = "Nessuna attività. Aggiungine una sopra.";
return;
}
messageElement.textContent = `Hai ${tasks.length} attività.`;
for (const task of tasks) {
const listItem = document.createElement("li");
listItem.className = task.completed ? "task completed" : "task";
listItem.innerHTML = `
<span class="task-title">${task.title}</span>
<div class="actions">
<button data-id="${task.id}" class="secondary complete-button">
${task.completed ? "Annulla" : "Fatto"}
</button>
<button data-id="${task.id}" class="danger delete-button">
Elimina
</button>
</div>
`;
taskListElement.appendChild(listItem);
}
}
Questa funzione:
- svuota la lista;
- controlla se ci sono attività;
- crea un
<li>per ogni attività; - aggiunge pulsanti per completare ed eliminare;
- mette tutto nella pagina.
Questo è rendering DOM.
I dati diventano HTML.
HTML diventa visibile.
L’utente si impressiona.
Forse.
Aggiungi una Nuova Attività
Ora aggiungi questa funzione:
function addTask(event) {
event.preventDefault();
const title = taskInput.value.trim();
if (title === "") {
messageElement.textContent = "Scrivi prima un’attività.";
return;
}
const newTask = {
id: Date.now(),
title: title,
completed: false
};
tasks.push(newTask);
saveTasks();
renderTasks();
taskInput.value = "";
}
Questa funzione:
- ferma il reload del form con
preventDefault; - legge il valore dell’input;
- valida input vuoto;
- crea un nuovo oggetto attività;
- lo aggiunge all’array;
- salva le attività;
- renderizza di nuovo;
- svuota l’input.
Questo è un workflow completo.
Input.
Validazione.
Aggiornamento dati.
Storage.
Aggiornamento DOM.
Molto frontend.
Molto bello.
Molto “forse sto davvero capendo JavaScript.”
Completa un'Attività
Ora ci serve una funzione che segna un’attività come completata.
Aggiungi questa:
function toggleTask(id) {
for (const task of tasks) {
if (task.id === id) {
task.completed = !task.completed;
}
}
saveTasks();
renderTasks();
}
Questa funzione riceve un id.
Poi trova l’attività corrispondente.
Poi inverte completed.
Se era false, diventa true.
Se era true, diventa false.
Questo lo fa:
task.completed = !task.completed;
Il ! significa “not”.
Semplice.
Potente.
Simbolo piccolo.
Grande personalità.
Elimina un'Attività
Aggiungi questa funzione:
function deleteTask(id) {
tasks = tasks.filter(function (task) {
return task.id !== id;
});
saveTasks();
renderTasks();
}
Questa usa filter.
Crea un nuovo array con tutte le attività tranne quella da eliminare.
Questa riga:
return task.id !== id;
significa:
Tieni ogni attività il cui id non è quello eliminato.
L’attività con id corrispondente sparisce.
Molto pulito.
Niente drama.
L’attività se ne va in silenzio.
A differenza di certi bug.
Gestisci i Click sui Pulsanti delle Attività
I pulsanti Completa ed Elimina vengono creati dinamicamente.
Quindi ascolteremo i click su tutta la lista.
Aggiungi questo:
function handleTaskListClick(event) {
const id = Number(event.target.dataset.id);
if (event.target.classList.contains("complete-button")) {
toggleTask(id);
}
if (event.target.classList.contains("delete-button")) {
deleteTask(id);
}
}
Questo usa:
event.target
per sapere quale pulsante è stato cliccato.
E questo:
event.target.dataset.id
legge il data-id dal pulsante.
Dato che i valori dataset sono stringhe, lo convertiamo in numero:
Number(event.target.dataset.id)
Questo si chiama event delegation.
Nome elegante.
Idea semplice.
Ascolta sul genitore.
Reagisci ai click dei figli.
Come un insegnante che guarda tutta la classe invece di stare vicino a un solo studente.
Cancella Tutte le Attività
Aggiungi questa funzione:
function clearTasks() {
tasks = [];
saveTasks();
renderTasks();
}
Questa rimuove tutte le attività dall’array.
Poi salva l’array vuoto.
Poi aggiorna la pagina.
Semplice.
Potente.
Leggermente pericoloso.
Per questo il pulsante è rosso.
I pulsanti rossi significano:
Pensaci prima di cliccare.
Di solito.
Inizializza l'App
Ora dobbiamo avviare l’app.
Aggiungi questa funzione:
async function startApp() {
const hasSavedTasks = loadTasksFromStorage();
if (!hasSavedTasks) {
await loadDefaultTasks();
}
renderTasks();
}
Questa fa:
- prova a caricare attività da local storage;
- se non esistono, carica attività iniziali da JSON;
- renderizza attività.
Questo dà memoria all’app.
Prima visita?
Carica attività predefinite.
Dopo?
Usa attività salvate.
Molto pratico.
Molto reale.
Aggiungi Event Listener
Aggiungi questo:
taskForm.addEventListener("submit", addTask);
taskListElement.addEventListener("click", handleTaskListClick);
clearButton.addEventListener("click", clearTasks);
startApp();
Ora l’app può reagire a:
- submit del form;
- click sui pulsanti attività;
- click sul pulsante cancella.
E infine:
startApp();
avvia tutto.
L’app si sveglia.
Speriamo di buon umore.
Codice JavaScript Completo
Ecco il script.js completo:
const taskForm = document.getElementById("taskForm");
const taskInput = document.getElementById("taskInput");
const taskListElement = document.getElementById("taskList");
const messageElement = document.getElementById("message");
const clearButton = document.getElementById("clearButton");
let tasks = [];
function saveTasks() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
function loadTasksFromStorage() {
const savedTasks = localStorage.getItem("tasks");
if (savedTasks === null) {
return false;
}
tasks = JSON.parse(savedTasks);
return true;
}
async function loadDefaultTasks() {
try {
const response = await fetch("tasks.json");
if (!response.ok) {
throw new Error("Impossibile caricare le attività iniziali.");
}
tasks = await response.json();
saveTasks();
} catch (error) {
messageElement.textContent = "Impossibile caricare le attività iniziali.";
console.log(error);
}
}
function renderTasks() {
taskListElement.innerHTML = "";
if (tasks.length === 0) {
messageElement.textContent = "Nessuna attività. Aggiungine una sopra.";
return;
}
messageElement.textContent = `Hai ${tasks.length} attività.`;
for (const task of tasks) {
const listItem = document.createElement("li");
listItem.className = task.completed ? "task completed" : "task";
listItem.innerHTML = `
<span class="task-title">${task.title}</span>
<div class="actions">
<button data-id="${task.id}" class="secondary complete-button">
${task.completed ? "Annulla" : "Fatto"}
</button>
<button data-id="${task.id}" class="danger delete-button">
Elimina
</button>
</div>
`;
taskListElement.appendChild(listItem);
}
}
function addTask(event) {
event.preventDefault();
const title = taskInput.value.trim();
if (title === "") {
messageElement.textContent = "Scrivi prima un’attività.";
return;
}
const newTask = {
id: Date.now(),
title: title,
completed: false
};
tasks.push(newTask);
saveTasks();
renderTasks();
taskInput.value = "";
}
function toggleTask(id) {
for (const task of tasks) {
if (task.id === id) {
task.completed = !task.completed;
}
}
saveTasks();
renderTasks();
}
function deleteTask(id) {
tasks = tasks.filter(function (task) {
return task.id !== id;
});
saveTasks();
renderTasks();
}
function handleTaskListClick(event) {
const id = Number(event.target.dataset.id);
if (event.target.classList.contains("complete-button")) {
toggleTask(id);
}
if (event.target.classList.contains("delete-button")) {
deleteTask(id);
}
}
function clearTasks() {
tasks = [];
saveTasks();
renderTasks();
}
async function startApp() {
const hasSavedTasks = loadTasksFromStorage();
if (!hasSavedTasks) {
await loadDefaultTasks();
}
renderTasks();
}
taskForm.addEventListener("submit", addTask);
taskListElement.addEventListener("click", handleTaskListClick);
clearButton.addEventListener("click", clearTasks);
startApp();
Questo è il progetto completo.
Non più codice giocattolo.
Ancora adatto ai principianti.
Ma reale.
Ora hai stato, rendering, eventi, storage, validazione e fetch.
JavaScript non è più solo console.log.
Sta costruendo cose.
Pericoloso.
Nel modo giusto.
Testa il Progetto
Avvia il server locale:
caddy file-server --listen :8080
Apri:
http://localhost:8080
Ora testa:
- le attività iniziali si caricano da
tasks.json; - aggiungere un’attività vuota mostra un avviso;
- aggiungere un’attività reale funziona;
- cliccare Fatto segna un’attività come completata;
- cliccare Annulla la ripristina;
- cliccare Elimina rimuove un’attività;
- aggiornare la pagina mantiene le attività salvate;
- Cancella Tutte rimuove tutte le attività.
Se tutto funziona, complimenti.
Hai costruito una piccola app JavaScript completa.
Se qualcosa si rompe, complimenti lo stesso.
Stai facendo vera programmazione.
La vera programmazione è spesso:
Perché non funziona?
Ah.
Era una lettera.
Errori Comuni
Dimenticare il Server Locale
Se le attività non si caricano, controlla come hai aperto la pagina.
Sbagliato:
file:///home/user/javascript-lesson12/index.html
Meglio:
caddy file-server --listen :8080
Poi:
http://localhost:8080
Fetch vuole un server.
Anche piccolo.
I browser sono severi.
Come insegnanti con certificati di sicurezza.
Nome File JSON Sbagliato
Se il codice dice:
fetch("tasks.json")
allora il file deve chiamarsi:
tasks.json
Non:
task.json
Non:
Tasks.json
Non:
final-boss-data.json
I nomi dei file contano.
I computer non sono emotivamente flessibili.
Dimenticare JSON.stringify
Sbagliato:
localStorage.setItem("tasks", tasks);
Corretto:
localStorage.setItem("tasks", JSON.stringify(tasks));
Gli array devono essere convertiti in stringhe prima del salvataggio.
Altrimenti JavaScript ti dà zuppa.
Non zuppa buona.
Dimenticare JSON.parse
Sbagliato:
tasks = localStorage.getItem("tasks");
Corretto:
tasks = JSON.parse(savedTasks);
Quando leggi JSON da local storage, fai parse.
Stringify quando salvi.
Parse quando carichi.
Prepara valigia.
Disfa valigia.
Non indossare la valigia.
Pratica
Aggiungi un contatore attività che mostra:
Completate: 2 / 5
Suggerimento:
Puoi contare le attività completate così:
const completedTasks = tasks.filter(function (task) {
return task.completed;
});
Poi usa:
completedTasks.length
Questo ti farà praticare:
- array;
- filter;
- aggiornamenti DOM;
- logica di rendering.
Una piccola funzionalità.
Un miglioramento utile.
Una piccola vittoria.
Mini Challenge
Aggiungi priorità alle attività.
Ogni attività dovrebbe avere:
- title;
- priority.
Per esempio:
{
id: Date.now(),
title: "Learn fetch",
priority: "high",
completed: false
}
Aggiungi un <select> nell’HTML:
<select id="priorityInput">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
Poi mostra la priorità dentro ogni card attività.
Bonus:
Usa testi diversi per priorità diverse.
Non serve renderlo perfetto.
Fallo funzionare.
Poi migliora.
È così che crescono i progetti.
Prima patata.
Poi patata lucidata.
Poi prodotto.
Riepilogo
Oggi hai costruito un progetto JavaScript completo per principianti.
Hai usato:
- variabili per salvare riferimenti e dati;
- array per salvare attività;
- oggetti per rappresentare attività;
- funzioni per organizzare la logica;
- metodi DOM per renderizzare attività;
- eventi per reagire agli utenti;
- form per raccogliere input;
- validazione per evitare attività vuote;
- local storage per ricordare attività;
- fetch per caricare dati iniziali;
- JSON per salvare dati strutturati.
Questo è un grande risultato.
Non hai solo imparato sintassi.
Hai costruito qualcosa.
Questa è la differenza.
La sintassi è vocabolario.
I progetti sono conversazione.
Ora JavaScript non è più solo teoria.
È uno strumento.
Uno strumento strano.
A volte drammatico.
A volte confuso.
Ma molto potente.
Corso Completato
Hai completato il corso JavaScript per principianti.
Ora conosci le idee principali per continuare con:
- TypeScript;
- React;
- Next.js;
- Astro;
- progetti frontend;
- siti basati su API;
- applicazioni full-stack.
Non correre.
Pratica.
Costruisci piccole cose.
Rompile.
Sistemale.
Poi costruisci cose un po’ più grandi.
Questo è il percorso.
Non glamour.
Non istantaneo.
Ma reale.
E ora hai abbastanza JavaScript per iniziare a camminare.
Complimenti.
Il final boss è stato sconfitto.
Per ora.
JavaScript ha sempre un altro boss nascosto da qualche parte.