← Back to course

Progetto Finale

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:

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:

Questo è un vero progetto frontend.

Piccolo.

Utile.

Molto JavaScript.

Cosa Costruirai

Costruirai un task manager.

L’utente potrà:

L’app salverà anche le attività in local storage.

Quindi, se l’utente aggiorna la pagina, le attività resteranno lì.

Questo significa che combineremo:

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:

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à:

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:

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:

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:

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:

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:

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:

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:

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:

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:

Una piccola funzionalità.

Un miglioramento utile.

Una piccola vittoria.

Mini Challenge

Aggiungi priorità alle attività.

Ogni attività dovrebbe avere:

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:

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:

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.