rinomina esercizi js
This commit is contained in:
206
javascript/10_localStorage/index.html
Normal file
206
javascript/10_localStorage/index.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hub Esercizi localStorage</title>
|
||||
<style>
|
||||
/* RESET & BASE */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* CONTENITORE PRINCIPALE */
|
||||
.hub-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 750px;
|
||||
padding: 40px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
p.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 40px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* TITOLI SEZIONI */
|
||||
h2 {
|
||||
color: #333;
|
||||
font-size: 1.3rem;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #007bff;
|
||||
}
|
||||
|
||||
h2:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* LISTA CARD */
|
||||
.exercise-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
/* CARD ESERCIZIO */
|
||||
.card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
background: #fff;
|
||||
border: 2px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 5px 15px rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
/* ICONA E TESTI */
|
||||
.icon {
|
||||
font-size: 2rem;
|
||||
margin-right: 20px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info h3 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #2c3e50;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 0;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* TAG DIFFICOLTA' */
|
||||
.difficulty {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.difficulty.tutorial {
|
||||
background: #e3f2fd;
|
||||
color: #1565c0;
|
||||
}
|
||||
|
||||
.difficulty.easy {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.difficulty.medium {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.difficulty.hard {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* NOTA IMPORTANTE */
|
||||
.important-note {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 30px;
|
||||
font-size: 0.95rem;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.important-note strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.important-note code {
|
||||
background: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="hub-container">
|
||||
<h1>localStorage</h1>
|
||||
<p class="subtitle">Salvataggio Dati nel Browser</p>
|
||||
|
||||
<h2>Tutorial Introduttivo</h2>
|
||||
<div class="exercise-list">
|
||||
<a href="tutorial/index.html" class="card">
|
||||
<div class="icon">🧪</div>
|
||||
<div class="info">
|
||||
<h3>Tutorial localStorage <span class="difficulty tutorial">Tutorial</span></h3>
|
||||
<p>Impara a salvare e recuperare dati dal browser</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2>Esercizi</h2>
|
||||
<div class="exercise-list">
|
||||
<a href="watchlist/index.html" class="card">
|
||||
<div class="icon">📺</div>
|
||||
<div class="info">
|
||||
<h3>Watchlist (Film/Serie) <span class="difficulty easy">Facile</span></h3>
|
||||
<p>Aggiungi film, salva con localStorage, rimuovi dalla lista</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="spese/index.html" class="card">
|
||||
<div class="icon">💰</div>
|
||||
<div class="info">
|
||||
<h3>Gestore Spese <span class="difficulty easy">Facile</span></h3>
|
||||
<p>Aggiungi spese per categoria, calcola statistiche, salva e visualizza</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
54
javascript/10_localStorage/spese/index.html
Normal file
54
javascript/10_localStorage/spese/index.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Gestore Spese</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<a href="../index.html" style="position: absolute; top: 20px; left: 20px; text-decoration: none; color: #555; font-weight: bold;">← Dashboard</a>
|
||||
|
||||
<h1>💰 Gestore Spese</h1>
|
||||
|
||||
<div class="input-group">
|
||||
<select id="categoria">
|
||||
<option value="">Categoria</option>
|
||||
<option value="Cibo">🍔 Cibo</option>
|
||||
<option value="Trasporti">🚗 Trasporti</option>
|
||||
<option value="Intrattenimento">🎬 Intrattenimento</option>
|
||||
<option value="Utenze">💡 Utenze</option>
|
||||
<option value="Salute">🏥 Salute</option>
|
||||
<option value="Altro">📦 Altro</option>
|
||||
</select>
|
||||
<input type="number" id="importo" placeholder="Importo (€)" min="0" step="0.01">
|
||||
<input type="text" id="descrizione" placeholder="Descrizione">
|
||||
<button id="btnAggiungi">Aggiungi</button>
|
||||
</div>
|
||||
|
||||
<div class="stats-box">
|
||||
<div class="stat">
|
||||
<span class="stat-label">Totale</span>
|
||||
<span class="stat-value">€ <span id="totaleSpese">0.00</span></span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">N° Spese</span>
|
||||
<span class="stat-value"><span id="numeroSpese">0</span></span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-label">Media</span>
|
||||
<span class="stat-value">€ <span id="mediaSpesa">0.00</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Spese per Categoria</h2>
|
||||
<div id="categorieStime">
|
||||
<!-- Generato dinamicamente -->
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="btnRipristina" class="btn-delete">Elimina Tutte</button>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
239
javascript/10_localStorage/spese/script.js
Normal file
239
javascript/10_localStorage/spese/script.js
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* ESERCIZIO: Gestore Spese con localStorage
|
||||
*
|
||||
* OBIETTIVO:
|
||||
* Creare un'app che permette all'utente di aggiungere spese, salvarle in localStorage,
|
||||
* visualizzarle per categoria e calcolare statistiche.
|
||||
*
|
||||
* STRUTTURA DATI:
|
||||
* Array di oggetti spese:
|
||||
* [
|
||||
* { id: 1, categoria: "Cibo", importo: 25.50, descrizione: "Cena", data: "2026-02-06" },
|
||||
* { id: 2, categoria: "Trasporti", importo: 5.00, descrizione: "Benzina", data: "2026-02-06" }
|
||||
* ]
|
||||
*/
|
||||
|
||||
// SELEZIONE ELEMENTI DOM
|
||||
const selectCategoria = document.querySelector('#categoria');
|
||||
const inputImporto = document.querySelector('#importo');
|
||||
const inputDescrizione = document.querySelector('#descrizione');
|
||||
const btnAggiungi = document.querySelector('#btnAggiungi');
|
||||
const btnRipristina = document.querySelector('#btnRipristina');
|
||||
const btnEsporta = document.querySelector('#btnEsporta');
|
||||
|
||||
// STATO DELL'APP con alcuni esempi iniziali (per testare, da rimuovere o modificare in seguito)
|
||||
let spese = [
|
||||
{ id: 1, categoria: "Cibo", importo: 25.50, descrizione: "Cena", data: "2026-02-06" },
|
||||
{ id: 2, categoria: "Trasporti", importo: 5.00, descrizione: "Benzina", data: "2026-02-06" },
|
||||
{ id: 3, categoria: "Cibo", importo: 15.00, descrizione: "Pranzo", data: "2026-02-05" }
|
||||
];
|
||||
let ultimoId = 0;
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 1: Carica le spese dal localStorage
|
||||
*
|
||||
* Passi:
|
||||
* 1. Controlla se esiste la chiave "spese" in localStorage
|
||||
* 2. Se esiste, parsa il JSON e assegna a 'spese'
|
||||
* 3. Se esiste, trova il massimo ID per continuare da lì con ultimoId
|
||||
* 4. Se non esiste, lascia spese come array vuoto
|
||||
* 5. Chiama visualizzaLista() per mostrare i dati
|
||||
*/
|
||||
function caricaSpeseStorage() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 2: Salva tutte le spese nel localStorage
|
||||
*
|
||||
* Passi:
|
||||
* 1. Converti l'array 'spese' in JSON con JSON.stringify()
|
||||
* 2. Salva la stringa JSON in localStorage con chiave "spese"
|
||||
*/
|
||||
function salvaSpeseStorage() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 3: Aggiungi una nuova spesa
|
||||
*
|
||||
* Passi:
|
||||
* 1. Leggi i valori dagli input (categoria, importo, descrizione)
|
||||
* 2. Valida che categoria sia selezionata e importo > 0
|
||||
* 3. Se non valido, mostra un alert e fermati
|
||||
* 4. Crea un oggetto spesa con:
|
||||
* - id: incrementa ultimoId e usa il nuovo valore
|
||||
* - categoria: il valore selezionato
|
||||
* - importo: converti a numero float
|
||||
* - descrizione: il valore dell'input
|
||||
* - data: la data odierna in formato "YYYY-MM-DD" (usa new Date())
|
||||
* 5. Aggiungi l'oggetto all'array 'spese'
|
||||
* 6. Salva con salvaSpeseStorage()
|
||||
* 7. Svuota gli input
|
||||
* 8. Chiama visualizzaLista() per aggiornare l'UI
|
||||
*/
|
||||
function aggiungiSpesa() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 4: Elimina una spesa per indice
|
||||
*
|
||||
* Passi:
|
||||
* 1. Rimuovi l'elemento dall'array 'spese' usando splice(indice, 1)
|
||||
* 2. Salva con salvaSpeseStorage()
|
||||
* 3. Chiama visualizzaLista() per aggiornare l'UI
|
||||
*/
|
||||
function eliminaSpesa(indice) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 5: Calcola le statistiche
|
||||
*
|
||||
* Passi:
|
||||
* 1. Calcola il totale sommando tutti gli importi
|
||||
* 2. Conta il numero di spese
|
||||
* 3. Calcola la media: totale / numerospese (se numero > 0, altrimenti 0)
|
||||
* 4. Ritorna un oggetto con { totale, numero, media }
|
||||
*/
|
||||
function calcolaStatistiche() {
|
||||
let totale = 0;
|
||||
let numero = 0;
|
||||
let media = 0;
|
||||
|
||||
// TODO: Calcoli qui
|
||||
|
||||
return {
|
||||
totale: totale,
|
||||
numero: numero,
|
||||
media: media
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 6: Raggruppa spese per categoria
|
||||
*
|
||||
* Passi:
|
||||
* 1. Crea un oggetto vuoto: { }
|
||||
* 2. Per ogni spesa nell'array, aggiungi la categoria come chiave
|
||||
* 3. Se la categoria non esiste ancora, crea un array vuoto
|
||||
* 4. Aggiungi la spesa all'array della categoria
|
||||
* 5. Ritorna l'oggetto raggruppato
|
||||
*
|
||||
* Risultato atteso:
|
||||
* {
|
||||
* "Cibo": [spesa1, spesa2],
|
||||
* "Trasporti": [spesa3],
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
function raggruppaSpesePerCategoria() {
|
||||
let categorie = {};
|
||||
spese.forEach(spesa => {
|
||||
if (!categorie[spesa.categoria]) {
|
||||
categorie[spesa.categoria] = [];
|
||||
}
|
||||
categorie[spesa.categoria].push(spesa);
|
||||
});
|
||||
|
||||
return categorie;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 7: Visualizza e aggiorna tutta l'interfaccia
|
||||
*
|
||||
* Passi:
|
||||
* 1. Chiama calcolaStatistiche() e salva il risultato
|
||||
* 2. Aggiorna i valori HTML:
|
||||
* - '#totaleSpese' con il totale formattato (2 decimali)
|
||||
* - '#numeroSpese' con il numero di spese
|
||||
* - '#mediaSpesa' con la media formattata (2 decimali)
|
||||
* 3. Chiama raggruppaSpesePerCategoria() per ottenere le categorie
|
||||
* 4. Pulisci il contenuto di '#categorieStime'
|
||||
* 5. Per ogni categoria raggruppata:
|
||||
* - Crea un div con classe 'categoria-card'
|
||||
* - Aggiungi un header con il nome categoria e totale
|
||||
* - Aggiungi una lista (<ul>) con classe 'spese-list'
|
||||
* - Per ogni spesa della categoria, crea un <li> con classe 'spesa-item'
|
||||
* con: descrizione, importo, data e pulsante elimina
|
||||
* - Il pulsante elimina deve chiamare eliminaSpesa(indice) al click
|
||||
* 6. Se non ci sono spese, mostra un messaggio "Nessuna spesa"
|
||||
*
|
||||
* NOTA: Usa toFixed(2) per formattare i numeri a 2 decimali
|
||||
*/
|
||||
function visualizzaLista() {
|
||||
let stats = calcolaStatistiche();
|
||||
document.querySelector('#totaleSpese').textContent = stats.totale.toFixed(2) + " €";
|
||||
document.querySelector('#numeroSpese').textContent = stats.numero;
|
||||
document.querySelector('#mediaSpesa').textContent = stats.media.toFixed(2) + " €";
|
||||
|
||||
let categorie = raggruppaSpesePerCategoria();
|
||||
let containerCategorie = document.querySelector('#categorieStime');
|
||||
containerCategorie.innerHTML = '';
|
||||
|
||||
if (spese.length === 0) {
|
||||
containerCategorie.innerHTML = '<p>Nessuna spesa aggiunta.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (let categoria in categorie) {
|
||||
let divCategoria = document.createElement('div');
|
||||
divCategoria.classList.add('categoria-card');
|
||||
|
||||
let header = document.createElement('h3');
|
||||
let totaleCategoria = categorie[categoria].reduce((sum, spesa) => sum + spesa.importo, 0);
|
||||
header.textContent = `${categoria} - Totale: ${totaleCategoria.toFixed(2)} €`;
|
||||
divCategoria.appendChild(header);
|
||||
|
||||
let ulSpese = document.createElement('ul');
|
||||
ulSpese.classList.add('spese-list');
|
||||
|
||||
categorie[categoria].forEach(spesa => {
|
||||
let liSpesa = document.createElement('li');
|
||||
liSpesa.classList.add('spesa-item');
|
||||
liSpesa.innerHTML = `
|
||||
<span>${spesa.descrizione} - ${spesa.importo.toFixed(2)} € - ${spesa.data}</span>
|
||||
<button class="btn-elimina" onclick="eliminaSpesa(${spese.indexOf(spesa)})">Elimina</button>
|
||||
`;
|
||||
ulSpese.appendChild(liSpesa);
|
||||
});
|
||||
|
||||
divCategoria.appendChild(ulSpese);
|
||||
containerCategorie.appendChild(divCategoria);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE 8: Ripristina tutto
|
||||
*
|
||||
* Passi:
|
||||
* 1. Chiedi conferma con confirm()
|
||||
* 2. Se confermato:
|
||||
* - Svuota l'array spese: spese = []
|
||||
* - Resetta ultimoId = 0
|
||||
* - Elimina la chiave "spese" da localStorage
|
||||
* - Chiama visualizzaLista()
|
||||
*/
|
||||
function ripristinaTutto() {
|
||||
// Si vede solo nel browser e non nel plugin di vscode
|
||||
if (confirm("Sei sicuro di voler eliminare tutte le spese?")) {
|
||||
spese = [];
|
||||
ultimoId = 0;
|
||||
localStorage.removeItem("spese");
|
||||
visualizzaLista();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== EVENT LISTENER =====
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
caricaSpeseStorage();
|
||||
visualizzaLista();
|
||||
});
|
||||
|
||||
btnAggiungi.addEventListener('click', aggiungiSpesa);
|
||||
btnRipristina.addEventListener('click', ripristinaTutto);
|
||||
167
javascript/10_localStorage/spese/style.css
Normal file
167
javascript/10_localStorage/spese/style.css
Normal file
@@ -0,0 +1,167 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 2rem auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* INPUT GROUP */
|
||||
.input-group {
|
||||
background: #eee;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
padding: 8px;
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #218838;
|
||||
}
|
||||
|
||||
/* STATISTICHE */
|
||||
.stats-box {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.stat {
|
||||
background: #f0f0f0;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: block;
|
||||
font-size: 1.3rem;
|
||||
font-weight: bold;
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* CATEGORIE */
|
||||
.categoria-card {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.categoria-card h3 {
|
||||
margin: 0 0 0.6rem 0;
|
||||
font-size: 0.95rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.spese-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.spesa-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.4rem;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spesa-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.spesa-item span {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.btn-elimina {
|
||||
padding: 3px 6px;
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-elimina:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.nessuna-spesa {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 1.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* BUTTON GROUP */
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #dc3545;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
93
javascript/10_localStorage/tutorial/index.html
Normal file
93
javascript/10_localStorage/tutorial/index.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JSON e LocalStorage</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="controls">
|
||||
<h1>Tutorial: JSON e LocalStorage</h1>
|
||||
<button id="btn-esegui">▶️ Esegui Codice</button>
|
||||
<button id="btn-reset">🧹 Pulisci Tutto</button>
|
||||
<a href="../index.html" style="position: absolute; top: 20px; left: 20px; text-decoration: none; color: #555; font-weight: bold;">← Dashboard</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function mostraOutput(stepNumero, messaggio) {
|
||||
const elemento = document.getElementById(`output-${stepNumero}`);
|
||||
if (elemento) {
|
||||
messaggio = messaggio || "❌";
|
||||
let isObject = (typeof messaggio === 'object' && messaggio !== null);
|
||||
elemento.textContent = isObject ? JSON.stringify(messaggio, null, 2) : String(messaggio);
|
||||
elemento.classList.remove("loading");
|
||||
|
||||
elemento.style.transition = "";
|
||||
elemento.style.backgroundColor = "#666";
|
||||
setTimeout(() => {
|
||||
elemento.style.transition = "background-color 1s ease";
|
||||
elemento.style.backgroundColor = "#2d3436";
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
{
|
||||
title: "1. Serializzazione (Oggetto -> Stringa)",
|
||||
description: "Convertiamo un oggetto JS in una stringa JSON per poterla salvare.",
|
||||
outputLabel: "Risultato in pagina:"
|
||||
},
|
||||
{
|
||||
title: "2. Deserializzazione (Stringa -> Oggetto)",
|
||||
description: "Trasformiamo una stringa JSON ricevuta in un oggetto utilizzabile.",
|
||||
outputLabel: "Proprietà 'utente' estratta:"
|
||||
},
|
||||
{
|
||||
title: "3. Scrittura nel LocalStorage",
|
||||
description: "Salviamo le preferenze utente nel browser.",
|
||||
outputLabel: "Verifica lettura immediata:"
|
||||
},
|
||||
{
|
||||
title: "4. Lettura dal LocalStorage",
|
||||
description: "Recuperiamo e utilizziamo un dato salvato in precedenza.",
|
||||
outputLabel: "Tema recuperato:"
|
||||
},
|
||||
{
|
||||
title: "5. Gestione Array",
|
||||
description: "Aggiungiamo un film alla lista e salviamo tutto.",
|
||||
outputLabel: "Contenuto salvato nel Storage:"
|
||||
},
|
||||
{
|
||||
title: "6. Pattern \"Carica o Inizializza\"",
|
||||
description: "Gestiamo il caso in cui i dati non esistano ancora (evitiamo errori).",
|
||||
outputLabel: "Valore della lista sicura:"
|
||||
}
|
||||
].forEach((step, i) => {
|
||||
document.write(`
|
||||
<div class="step-card">
|
||||
<div class="step-header">
|
||||
<div class="step-title">${step.title}</div>
|
||||
</div>
|
||||
<div class="step-desc">${step.description}</div>
|
||||
<span class="label">${step.outputLabel}</span>
|
||||
<div id="output-${i + 1}" class="output-box"></div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
</script>
|
||||
<script src="tutorial_interattivo.js"></script>
|
||||
<script>
|
||||
document.getElementById('btn-reset').addEventListener('click', () => {
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
});
|
||||
document.getElementById('btn-esegui').addEventListener('click', () => {
|
||||
const el = document.querySelectorAll('[id^="output-"]');
|
||||
el.forEach(box => box.className = "output-box loading");
|
||||
eseguiTutorial();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
109
javascript/10_localStorage/tutorial/script.js
Normal file
109
javascript/10_localStorage/tutorial/script.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// TUTORIAL INTERATTIVO: JSON e LocalStorage
|
||||
|
||||
// Funzione pricipale del tutorial
|
||||
function eseguiTutorial() {
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 1. Convertire Oggetti in Stringhe (Serializzazione) ===
|
||||
* Il LocalStorage può salvare SOLO stringhe.
|
||||
* Per salvare un oggetto o un array, dobbiamo convertirlo usando JSON.stringify().
|
||||
*/
|
||||
const studente = {
|
||||
nome: "Alessandro",
|
||||
corso: "JavaScript",
|
||||
voto: 8
|
||||
};
|
||||
|
||||
// TODO: Converti l'oggetto 'studente' in una stringa JSON e salvala nella variabile 'studenteStringa'.
|
||||
let studenteStringa = "";
|
||||
|
||||
// Visualizzazione Risultato
|
||||
mostraOutput(1, studenteStringa);
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 2. Convertire Stringhe in Oggetti (Deserializzazione) ===
|
||||
* Quando leggiamo dal LocalStorage, otteniamo una stringa.
|
||||
* Per riutilizzarla come codice JS, dobbiamo convertirla indietro usando JSON.parse().
|
||||
*/
|
||||
const datiRicevuti = '{"id": 101, "utente": "Alessandro", "attivo": true}';
|
||||
|
||||
// TODO: Converti la stringa 'datiRicevuti' in un vero oggetto JavaScript e salvalo in 'oggettoDati'.
|
||||
// Nota: Inizializziamo a {} vuoto per evitare errori se non completi il TODO
|
||||
let oggettoDati = {};
|
||||
|
||||
// Visualizzazione Risultato (mostriamo solo la proprietà 'utente')
|
||||
mostraOutput(2, oggettoDati.utente);
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 3. Salvare nel LocalStorage ===
|
||||
* Usiamo localStorage.setItem('chiave', 'valore') per salvare i dati.
|
||||
* Ricorda: il valore deve essere una stringa!
|
||||
*/
|
||||
const preferenze = {
|
||||
tema: "scuro",
|
||||
notifiche: true
|
||||
};
|
||||
|
||||
// TODO: Salva l'oggetto 'preferenze' (convertito in stringa!) nel localStorage con la chiave "impostazioni_utente".
|
||||
// SUGGERIMENTO: Usa JSON.stringify(preferenze) dentro il setItem.
|
||||
|
||||
|
||||
// Visualizzazione Risultato (Leggiamo direttamente dallo storage per verificare)
|
||||
const verificaStorage = localStorage.getItem("impostazioni_utente");
|
||||
mostraOutput(3, verificaStorage ? "Salvato con successo: " + verificaStorage : "");
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 4. Leggere dal LocalStorage ===
|
||||
* Usiamo localStorage.getItem('chiave') per recuperare i dati.
|
||||
*/
|
||||
|
||||
// TODO: Recupera la stringa salvata alla chiave "impostazioni_utente", convertila in oggetto e assegna il tema a 'temaSalvato'.
|
||||
let temaSalvato = "";
|
||||
|
||||
// Visualizzazione Risultato
|
||||
mostraOutput(4, temaSalvato);
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 5. Gestire gli Array (Liste) ===
|
||||
* Spesso salviamo liste di cose (es. dipendenti, libri).
|
||||
* Il processo è identico: Array -> JSON.stringify -> Storage.
|
||||
*/
|
||||
const nuoviFilm = [
|
||||
{ titolo: "Inception", anno: 2010 },
|
||||
{ titolo: "Matrix", anno: 1999 }
|
||||
];
|
||||
|
||||
// TODO: 1. Aggiungi un nuovo film all'array 'nuoviFilm' usando .push() (es. { titolo: "Interstellar", anno: 2014 }).
|
||||
// TODO: 2. Salva l'intero array aggiornato nel localStorage con chiave "lista_film" (ricorda JSON.stringify).
|
||||
|
||||
|
||||
// Visualizzazione Risultato
|
||||
mostraOutput(5, localStorage.getItem("lista_film"));
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 6. Il Pattern "Carica o Inizializza" ===
|
||||
* Quando l'app parte, dobbiamo caricare i dati.
|
||||
* Ma se è la prima volta che l'utente entra, il localStorage sarà null.
|
||||
*/
|
||||
|
||||
// Immagina di provare a caricare una chiave che non esiste:
|
||||
const chiaveInesistente = localStorage.getItem("chiave_mai_usata");
|
||||
|
||||
// TODO: Completa la riga sotto per assegnare a 'listaSicura' il valore parsato di 'chiaveInesistente',
|
||||
// OPPURE un array vuoto [] se il dato è null.
|
||||
let listaSicura = null;
|
||||
|
||||
// Visualizzazione Risultato
|
||||
mostraOutput(6, listaSicura);
|
||||
}
|
||||
118
javascript/10_localStorage/tutorial/styles.css
Normal file
118
javascript/10_localStorage/tutorial/styles.css
Normal file
@@ -0,0 +1,118 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
color: #333;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #1a73e8;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
z-index: 100;
|
||||
background-color: white;
|
||||
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
background-color: #1a73e8;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
margin: 5px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
button#btn-reset {
|
||||
background-color: #ea4335;
|
||||
}
|
||||
|
||||
.step-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 25px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto 25px auto;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-left: 6px solid #1a73e8;
|
||||
}
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
color: #1557b0;
|
||||
}
|
||||
|
||||
.step-desc {
|
||||
font-size: 0.95em;
|
||||
color: #5f6368;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.output-box {
|
||||
background: #2d3436;
|
||||
color: #00c900;
|
||||
padding: 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
border-radius: 6px;
|
||||
min-height: 20px;
|
||||
white-space: pre-wrap;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.output-box.loading::after {
|
||||
content: "⏳ Attesa server...";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
font-size: 0.8em;
|
||||
color: #e5c07b;
|
||||
}
|
||||
|
||||
.output-box.error {
|
||||
border: 2px solid #e06c75;
|
||||
color: #e06c75;
|
||||
}
|
||||
|
||||
.output-box.success {
|
||||
border-left: 4px solid #98c379;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 0.8em;
|
||||
color: #bbbbbb;
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
25
javascript/10_localStorage/watchlist/index.html
Normal file
25
javascript/10_localStorage/watchlist/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>La Mia Watchlist</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<a href="../index.html" style="position: absolute; top: 20px; left: 20px; text-decoration: none; color: #555; font-weight: bold;">← Dashboard</a>
|
||||
|
||||
<h1>🎬 Film da Vedere</h1>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" id="titolo" placeholder="Titolo film">
|
||||
<input type="number" id="anno" placeholder="Anno" style="max-width: 80px;">
|
||||
<button id="btn-add">Aggiungi</button>
|
||||
</div>
|
||||
|
||||
<ul id="lista-film">
|
||||
<!-- I film verranno aggiunti qui dinamicamente -->
|
||||
</ul>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
117
javascript/10_localStorage/watchlist/script.js
Normal file
117
javascript/10_localStorage/watchlist/script.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Esercizio: Watchlist Film
|
||||
*
|
||||
* CONTESTO:
|
||||
* Abbiamo un HTML con due input (titolo, anno) e una lista vuota.
|
||||
* Vogliamo salvare la lista dei film da vedere nel browser.
|
||||
*
|
||||
* STRUTTURA DATI:
|
||||
* Array di oggetti: [{ titolo: "Dune", anno: "2021" }, ...]
|
||||
*
|
||||
* ISTRUZIONI:
|
||||
* 1. Completa 'caricaFilm' per leggere dal localStorage (o inizializzare array vuoto).
|
||||
* 2. Completa 'aggiungiFilm' per aggiornare l'array, salvarlo e aggiornare la UI.
|
||||
* 3. Usa JSON.parse e JSON.stringify.
|
||||
*/
|
||||
|
||||
// RIFERIMENTI DOM
|
||||
const inputTitolo = document.querySelector('#titolo');
|
||||
const inputAnno = document.querySelector('#anno');
|
||||
const listaFilm = document.querySelector('#lista-film');
|
||||
const btnAdd = document.querySelector('#btn-add');
|
||||
|
||||
// STATO DELL'APP con alcuni esempi iniziali (per testare, da rimuovere o modificare in seguito)
|
||||
let filmWatchlist = [
|
||||
{ titolo: "Dune", anno: "2021" },
|
||||
{ titolo: "Inception", anno: "2010" },
|
||||
{ titolo: "Matrix", anno: "1999" },
|
||||
{ titolo: "Interstellar", anno: "2014" },
|
||||
{ titolo: "Il Signore degli Anelli: La Compagnia dell'Anello", anno: "2001" },
|
||||
{ titolo: "Harry Potter e la Pietra Filosofale", anno: "2001" }
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* STEP 1: Caricamento Iniziale
|
||||
* Leggi la chiave "my_watchlist" dal localStorage.
|
||||
* Se esiste, parsa la stringa e riempi l'array 'filmWatchlist'.
|
||||
* Se non esiste, lascia l'array vuoto.
|
||||
* Infine chiama visualizzaLista().
|
||||
*/
|
||||
function caricaLista() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* STEP 2: Funzione Renderizza (visualizza) la lista
|
||||
* Pulisce l'HTML e ricrea gli elementi basandosi sull'array filmWatchlist
|
||||
* 1. Pulisci il contenuto di 'listaFilm'.
|
||||
* 2. Per ogni film in 'filmWatchlist', crea un <li> con dentro --> <p>titolo (anno)</p>
|
||||
* 3. Aggiungi l'<li> a 'listaFilm'.
|
||||
*/
|
||||
function visualizzaLista() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* STEP 3: Aggiunta e Salvataggio
|
||||
* Quando l'utente clicca "Aggiungi Film":
|
||||
* 1. Cattura i valori dagli input.
|
||||
* 2. Crea un oggetto film solo se entrambi i campi hanno un valore.
|
||||
* 3. Crea un oggetto con i dati degli input.
|
||||
* 4. Pushalo nell'array filmWatchlist.
|
||||
* 5. Salva l'intero array aggiornato nel localStorage (JSON.stringify!).
|
||||
* 6. Chiama visualizzaLista().
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
// AVVIO
|
||||
caricaLista();
|
||||
|
||||
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/************************* ESERCIZI EXTRA *******************************/
|
||||
/************************************************************************/
|
||||
|
||||
/**
|
||||
* Esercizio Extra 1: Rimuovere Elementi
|
||||
*
|
||||
* OBIETTIVO:
|
||||
* Aggiungere un bottone "Elimina" accanto a ogni film che rimuova il film
|
||||
* dall'array E aggiorni il localStorage.
|
||||
*
|
||||
* Questo esercizio RICHIEDE di modificare la funzione 'visualizzaLista'.
|
||||
*
|
||||
* Possibile Implementazione:
|
||||
* 1. Usare un ciclo for con la variabile indice (let i = 0; i < ...; i++).
|
||||
* 2. Dentro il ciclo crea un bottone con testo "Elimina" o "X" (mettere anche la classe corretta già presente in CSS).
|
||||
* 3. Aggiungi un eventListener al bottone che:
|
||||
* - Usa l'indice 'i' per fare filmWatchlist.splice(i, 1) e rimuovere l'elemento.
|
||||
* - Salva l'array aggiornato nel localStorage.
|
||||
* 4. Richiamare visualizzaLista().
|
||||
* Nota: Ricorda che ogni volta che visualizzi la lista, stai ricreando TUTTI gli elementi HTML,
|
||||
* quindi i vecchi listener vengono persi. Devi rimetterli ogni volta.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Esercizio Extra 2: Stato "Visto/Non Visto"
|
||||
*
|
||||
* OBIETTIVO:
|
||||
* Aggiungi una checkbox accanto a ogni film.
|
||||
* Se l'utente la spunta, lo stato "visto" del film (true/false) deve essere salvato.
|
||||
*
|
||||
* LOGICA:
|
||||
* 1. Modifica l'oggetto iniziale del film aggiungendo la proprietà --> visto: false.
|
||||
* 2. Nella render, per ogni elemento <li>, crea un <input type="checkbox">.
|
||||
* 3. Imposta checkbox.checked = film.visto.
|
||||
* 4. All'evento 'change' della checkbox:
|
||||
* - Aggiorna filmWatchlist[index].visto = checkbox.checked
|
||||
* - Salva tutto nel localStorage.
|
||||
* - (Opzionale) Aggiungi una classe CSS per barrare il testo se visto --> text-decoration: line-through.
|
||||
* Nota: devi aggiornare la classe del <p> dentro l'li in base a film.visto.
|
||||
*/
|
||||
54
javascript/10_localStorage/watchlist/style.css
Normal file
54
javascript/10_localStorage/watchlist/style.css
Normal file
@@ -0,0 +1,54 @@
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 2rem auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
background: #eee;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
li {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background: #dc3545;
|
||||
margin-left: 10px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
Reference in New Issue
Block a user