Esercizio 11 API
This commit is contained in:
166
JS_Esercizi 11 - API/README.md
Normal file
166
JS_Esercizi 11 - API/README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 🔌 Esercizi API REST - Guida Completa
|
||||
|
||||
## 📋 Struttura del Progetto
|
||||
|
||||
```
|
||||
JS_Esercizi 11 - API/
|
||||
├── index.html # Hub principale - accesso agli esercizi
|
||||
├── tutorial/
|
||||
│ └── index.html # Tutorial su API REST e Fetch
|
||||
└── esercizi/
|
||||
├── meteo/
|
||||
│ └── index.html # Esercizio 1: GET Request base
|
||||
├── lista_utenti/
|
||||
│ └── index.html # Esercizio 2: GET con iterazione e DOM
|
||||
└── todo_app/
|
||||
└── index.html # Esercizio 3: CRUD completo (POST, PUT, DELETE)
|
||||
```
|
||||
|
||||
## 🚀 Come Iniziare
|
||||
|
||||
### 1. Avvia il Server API
|
||||
Prima di iniziare qualsiasi esercizio, **devi avviare il server**:
|
||||
|
||||
```bash
|
||||
cd server-api
|
||||
npm start
|
||||
```
|
||||
|
||||
Il server sarà disponibile a: `http://localhost:3000/api`
|
||||
|
||||
### ⚠️ Importante
|
||||
Se non avvii il server, gli esercizi non funzioneranno perché non riusciranno a recuperare i dati!
|
||||
|
||||
## 📚 Contenuto del Corso
|
||||
|
||||
### 🎓 Tutorial
|
||||
Leggi prima il tutorial per capire:
|
||||
- Cos'è un'API REST
|
||||
- Come funziona la Fetch API
|
||||
- Metodi HTTP (GET, POST, PUT, DELETE)
|
||||
- Promise e Async/Await
|
||||
|
||||
**Accedi da:** [index.html → Tutorial](tutorial/index.html)
|
||||
|
||||
### 🔧 Esercizio 1: App Meteo (GET Base)
|
||||
**Difficoltà:** ⭐ Base
|
||||
|
||||
Impara i fondamenti delle GET request:
|
||||
- Fetch API base
|
||||
- Conversione JSON
|
||||
- Visualizzazione dati
|
||||
- Gestione errori
|
||||
|
||||
**Cosa fare:** Scrivi il codice per recuperare e visualizzare dati meteo
|
||||
|
||||
**Accedi da:** [index.html → App Meteo](esercizi/meteo/index.html)
|
||||
|
||||
### 👥 Esercizio 2: Lista Utenti (GET + DOM)
|
||||
**Difficoltà:** ⭐⭐ Intermedio
|
||||
|
||||
Sviluppa skills di manipolazione del DOM:
|
||||
- GET request avanzato
|
||||
- Iterazione array con map()
|
||||
- Creazione dinamica di elementi HTML
|
||||
- Conteggio e statistiche
|
||||
|
||||
**Cosa fare:** Recupera una lista di utenti e crea card dinamiche
|
||||
|
||||
**Accedi da:** [index.html → Lista Utenti](esercizi/lista_utenti/index.html)
|
||||
|
||||
### ✓ Esercizio 3: Todo App (CRUD Completo)
|
||||
**Difficoltà:** ⭐⭐⭐ Avanzato
|
||||
|
||||
Implementa tutte le operazioni CRUD:
|
||||
- **CREATE:** POST request per aggiungere todo
|
||||
- **READ:** GET request per recuperare todo
|
||||
- **UPDATE:** PUT request per segnare come completato
|
||||
- **DELETE:** DELETE request per eliminare
|
||||
|
||||
**Cosa fare:** Crea un'app TODO completa con tutte le operazioni
|
||||
|
||||
**Accedi da:** [index.html → Todo App](esercizi/todo_app/index.html)
|
||||
|
||||
## 🛠️ API Endpoints Disponibili
|
||||
|
||||
Il server fornisce questi endpoint:
|
||||
|
||||
```
|
||||
GET /api/weather → Ottieni dati meteo
|
||||
GET /api/users → Ottieni lista utenti
|
||||
GET /api/users/:id → Ottieni un utente
|
||||
POST /api/users → Crea nuovo utente
|
||||
PUT /api/users/:id → Aggiorna utente
|
||||
DELETE /api/users/:id → Elimina utente
|
||||
|
||||
GET /api/todos → Ottieni lista todo
|
||||
GET /api/todos/:id → Ottieni un todo
|
||||
POST /api/todos → Crea nuovo todo
|
||||
PUT /api/todos/:id → Aggiorna todo
|
||||
DELETE /api/todos/:id → Elimina todo
|
||||
```
|
||||
|
||||
## 📖 Esempi di Codice
|
||||
|
||||
### GET Request Base
|
||||
```javascript
|
||||
const response = await fetch('http://localhost:3000/api/users');
|
||||
const users = await response.json();
|
||||
console.log(users);
|
||||
```
|
||||
|
||||
### POST Request
|
||||
```javascript
|
||||
const response = await fetch('http://localhost:3000/api/todos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Nuovo todo', completed: false })
|
||||
});
|
||||
const newTodo = await response.json();
|
||||
```
|
||||
|
||||
### DELETE Request
|
||||
```javascript
|
||||
const response = await fetch('http://localhost:3000/api/todos/1', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
```
|
||||
|
||||
## ✅ Checklist Completamento
|
||||
|
||||
- [ ] Ho avviato il server `npm start` nella cartella server-api
|
||||
- [ ] Ho letto il Tutorial
|
||||
- [ ] Ho completato l'Esercizio 1 (App Meteo)
|
||||
- [ ] Ho completato l'Esercizio 2 (Lista Utenti)
|
||||
- [ ] Ho completato l'Esercizio 3 (Todo App)
|
||||
|
||||
## 💡 Suggerimenti
|
||||
|
||||
1. **Leggi sempre il tutorial prima** di iniziare gli esercizi
|
||||
2. **Apri la console browser** (F12) per vedere i messaggi di errore
|
||||
3. **Utilizza network tab** in DevTools per vedere le richieste HTTP
|
||||
4. **Prova i comandi curl** nel terminale per testare gli endpoint
|
||||
5. **Inizia semplice:** completa prima le funzioni di base
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
**Errore: "Fetch failed"**
|
||||
- Controlla che il server sia avviato con `npm start`
|
||||
- Verifica che il URL sia corretto
|
||||
|
||||
**Errore: "response.json() is not a function"**
|
||||
- Controlla che la risposta sia valida JSON
|
||||
- Vedi la console per il messaggio di errore esatto
|
||||
|
||||
**Dati non visualizzati**
|
||||
- Controlla che il codice fetch sia implementato
|
||||
- Verifica che il DOM sia aggiornato correttamente
|
||||
- Usa `console.log()` per debuggare
|
||||
|
||||
## 📚 Risorse Utili
|
||||
|
||||
- [MDN - Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
|
||||
- [MDN - async/await](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises)
|
||||
- [JSON Server GitHub](https://github.com/typicode/json-server)
|
||||
|
||||
Buon lavoro! 🚀
|
||||
200
JS_Esercizi 11 - API/index.html
Normal file
200
JS_Esercizi 11 - API/index.html
Normal file
@@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hub Esercizi API</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: 650px;
|
||||
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;
|
||||
}
|
||||
|
||||
/* LISTA CARD */
|
||||
.exercise-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info h3 {
|
||||
margin: 0 0 5px 0;
|
||||
color: #2c3e50;
|
||||
font-size: 1.1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 0;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* TAG DIFFICOLTA' */
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
font-weight: bold;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.tutorial {
|
||||
background-color: #e3f2fd;
|
||||
color: #1565c0;
|
||||
border: 1px solid #bbdefb;
|
||||
}
|
||||
|
||||
/* Blu */
|
||||
.easy {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
/* Verde */
|
||||
.medium {
|
||||
background-color: #fff3e0;
|
||||
color: #ef6c00;
|
||||
}
|
||||
|
||||
/* Arancione */
|
||||
.hard {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* Rosso */
|
||||
|
||||
/* FRECCIA AL PASSAGGIO DEL MOUSE */
|
||||
.arrow {
|
||||
font-size: 1.5rem;
|
||||
color: #ddd;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.card:hover .arrow {
|
||||
color: #007bff;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="hub-container">
|
||||
<h1>Esercizi API</h1>
|
||||
<p class="subtitle">Corso Web Developer</p>
|
||||
|
||||
<div class="exercise-list">
|
||||
|
||||
<a href="tutorial/index.html" class="card">
|
||||
<div class="icon">🧪</div>
|
||||
<div class="info">
|
||||
<h3>Tutorial<span class="badge tutorial">Tutorial</span></h3>
|
||||
<p>Impara a comunicare con le API REST e recuperare dati dal server.</p>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
</a>
|
||||
|
||||
<a href="meteo/index.html" class="card">
|
||||
<div class="icon">🌤️</div>
|
||||
<div class="info">
|
||||
<h3>App Meteo <span class="badge easy">Base</span></h3>
|
||||
<p>Fetch API, GET request, manipolazione JSON.</p>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
</a>
|
||||
|
||||
<a href="lista_utenti/index.html" class="card">
|
||||
<div class="icon">👥</div>
|
||||
<div class="info">
|
||||
<h3>Lista Utenti <span class="badge medium">Intermedio</span></h3>
|
||||
<p>GET request, iterazione array, DOM manipulation.</p>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
</a>
|
||||
|
||||
<a href="todo_app/index.html" class="card">
|
||||
<div class="icon">✓</div>
|
||||
<div class="info">
|
||||
<h3>Todo App <span class="badge hard">Avanzato</span></h3>
|
||||
<p>POST, PUT, DELETE request, gestione dello stato.</p>
|
||||
</div>
|
||||
<div class="arrow">→</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
41
JS_Esercizi 11 - API/lista_utenti/index.html
Normal file
41
JS_Esercizi 11 - API/lista_utenti/index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lista Utenti - GET e DOM Manipulation</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>👥 Lista Utenti</h1>
|
||||
<p class="subtitle">Recupera e visualizza utenti dal server</p>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>📝 Obiettivo dell'Esercizio</h3>
|
||||
<p>Dovrai scrivere il codice JavaScript per:</p>
|
||||
<ol>
|
||||
<li>Recuperare la lista di utenti da <code>/api/users</code></li>
|
||||
<li>Iterare attraverso l'array di utenti</li>
|
||||
<li>Creare e inserire dinamicamente le card degli utenti nel DOM</li>
|
||||
<li>Mostrare il numero totale di utenti</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<button onclick="fetchAndDisplayUsers()">Carica Lista Utenti</button>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Caricamento utenti...</p>
|
||||
</div>
|
||||
|
||||
<div class="users-container" id="usersContainer">
|
||||
<!-- Le card degli utenti verranno inserite qui -->
|
||||
</div>
|
||||
|
||||
<a href="../index.html" class="back-button">← Torna al Hub</a>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
59
JS_Esercizi 11 - API/lista_utenti/script.js
Normal file
59
JS_Esercizi 11 - API/lista_utenti/script.js
Normal file
@@ -0,0 +1,59 @@
|
||||
// TODO: Completa questa funzione
|
||||
async function fetchAndDisplayUsers() {
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('usersContainer');
|
||||
|
||||
// 1. Mostra il loading spinner
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
// 2. Scrivi qui il fetch per ottenere gli utenti da:
|
||||
// http://localhost:3000/api/users
|
||||
// 3. Converti la risposta in JSON
|
||||
// 4. Passa i dati alla funzione displayUsers()
|
||||
|
||||
// SUGGERIMENTO:
|
||||
// const response = await fetch('...');
|
||||
// const users = await response.json();
|
||||
// displayUsers(users);
|
||||
|
||||
console.log('Scrivi il codice qui!');
|
||||
throw new Error('Codice non implementato');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Errore:', error);
|
||||
container.innerHTML = `
|
||||
<div class="error">
|
||||
<strong>❌ Errore:</strong> ${error.message}
|
||||
</div>
|
||||
`;
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Funzione helper per visualizzare gli utenti
|
||||
function displayUsers(users) {
|
||||
const container = document.getElementById('usersContainer');
|
||||
|
||||
if (!Array.isArray(users) || users.length === 0) {
|
||||
container.innerHTML = '<div class="empty">Nessun utente trovato</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Crea le card per ogni utente
|
||||
const cardsHTML = users.map(user => `
|
||||
<div class="user-card">
|
||||
<h3>${user.name || 'Nome sconosciuto'}</h3>
|
||||
<p>📧 <strong>Email:</strong> ${user.email || 'N/A'}</p>
|
||||
<p>📞 <strong>Telefono:</strong> ${user.phone || 'N/A'}</p>
|
||||
<p>🌐 <strong>Website:</strong> ${user.website || 'N/A'}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Aggiungi il contatore di utenti
|
||||
const counterHTML = `<div class="counter">📊 Totale: ${users.length} utenti</div>`;
|
||||
|
||||
container.innerHTML = cardsHTML + counterHTML;
|
||||
}
|
||||
179
JS_Esercizi 11 - API/lista_utenti/styles.css
Normal file
179
JS_Esercizi 11 - API/lista_utenti/styles.css
Normal file
@@ -0,0 +1,179 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #2196F3;
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.instructions h3 {
|
||||
color: #1565c0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions ol {
|
||||
margin-left: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #764ba2;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #667eea;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.users-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 15px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.user-card:hover {
|
||||
background: #f5f5f5;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-card h3 {
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.user-card p {
|
||||
color: #666;
|
||||
margin: 5px 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.user-card strong {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
color: #c62828;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 10px 20px;
|
||||
background: #999;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
.counter {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 10px;
|
||||
}
|
||||
45
JS_Esercizi 11 - API/meteo/index.html
Normal file
45
JS_Esercizi 11 - API/meteo/index.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>App Meteo - GET Request</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🌤️ App Meteo</h1>
|
||||
<p class="subtitle">Impara a fare GET request con Fetch API</p>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>📝 Obiettivo dell'Esercizio</h3>
|
||||
<p>Dovrai scrivere il codice JavaScript per:</p>
|
||||
<ol>
|
||||
<li>Recuperare una lista di dati meteo dal server usando <code>fetch()</code></li>
|
||||
<li>Visualizzare i dati ricevuti nella pagina</li>
|
||||
<li>Gestire gli errori di rete</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="cityInput">Cerca una città (opzionale):</label>
|
||||
<input type="text" id="cityInput" placeholder="es: Roma">
|
||||
</div>
|
||||
|
||||
<button onclick="fetchWeatherData()">Carica Dati Meteo</button>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Caricamento dati...</p>
|
||||
</div>
|
||||
|
||||
<div class="result" id="result">
|
||||
<div class="weather-info" id="weatherInfo"></div>
|
||||
</div>
|
||||
|
||||
<a href="../index.html" class="back-button">← Torna al Hub</a>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
65
JS_Esercizi 11 - API/meteo/script.js
Normal file
65
JS_Esercizi 11 - API/meteo/script.js
Normal file
@@ -0,0 +1,65 @@
|
||||
// TODO: Completa questa funzione
|
||||
async function fetchWeatherData() {
|
||||
const loading = document.getElementById('loading');
|
||||
const result = document.getElementById('result');
|
||||
const weatherInfo = document.getElementById('weatherInfo');
|
||||
|
||||
// 1. Mostra il loading spinner
|
||||
loading.style.display = 'block';
|
||||
result.classList.remove('show');
|
||||
|
||||
try {
|
||||
// 2. Scrivi qui il fetch per ottenere i dati da:
|
||||
// http://localhost:3000/api/weather
|
||||
// 3. Converti la risposta in JSON
|
||||
// 4. Mostra i dati nella pagina
|
||||
|
||||
// SUGGERIMENTO: Usa la struttura vista nel tutorial
|
||||
// const response = await fetch('...');
|
||||
// const data = await response.json();
|
||||
|
||||
console.log('Scrivi il codice qui!');
|
||||
throw new Error('Codice non implementato');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Errore:', error);
|
||||
result.classList.add('show');
|
||||
weatherInfo.innerHTML = `
|
||||
<div class="weather-detail error">
|
||||
<strong>❌ Errore:</strong> ${error.message}
|
||||
</div>
|
||||
`;
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Funzione helper per formattare la risposta
|
||||
function displayWeatherData(data) {
|
||||
const weatherInfo = document.getElementById('weatherInfo');
|
||||
const result = document.getElementById('result');
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
// Se è un array
|
||||
weatherInfo.innerHTML = data.map(item => `
|
||||
<div class="weather-detail success">
|
||||
<h3>${item.city || item.name || 'Città sconosciuta'}</h3>
|
||||
<p>🌡️ Temperatura: ${item.temperature || item.temp || 'N/A'}°C</p>
|
||||
<p>💨 Umidità: ${item.humidity || 'N/A'}%</p>
|
||||
<p>☁️ Condizioni: ${item.condition || item.description || 'N/A'}</p>
|
||||
</div>
|
||||
`).join('');
|
||||
} else {
|
||||
// Se è un singolo oggetto
|
||||
weatherInfo.innerHTML = `
|
||||
<div class="weather-detail success">
|
||||
<h3>${data.city || data.name || 'Città sconosciuta'}</h3>
|
||||
<p>🌡️ Temperatura: ${data.temperature || data.temp || 'N/A'}°C</p>
|
||||
<p>💨 Umidità: ${data.humidity || 'N/A'}%</p>
|
||||
<p>☁️ Condizioni: ${data.condition || data.description || 'N/A'}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
result.classList.add('show');
|
||||
}
|
||||
185
JS_Esercizi 11 - API/meteo/styles.css
Normal file
185
JS_Esercizi 11 - API/meteo/styles.css
Normal file
@@ -0,0 +1,185 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #2196F3;
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.instructions h3 {
|
||||
color: #1565c0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions ol {
|
||||
margin-left: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #764ba2;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #667eea;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.weather-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.weather-info h2 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.weather-detail {
|
||||
background: white;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: #e8f5e9;
|
||||
border-left-color: #4caf50;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 10px 20px;
|
||||
background: #999;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
66
JS_Esercizi 11 - API/todo_app/index.html
Normal file
66
JS_Esercizi 11 - API/todo_app/index.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Todo App - CRUD Operations</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>✓ Todo App</h1>
|
||||
<p class="subtitle">Operazioni CRUD (Create, Read, Update, Delete)</p>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>📝 Obiettivo dell'Esercizio</h3>
|
||||
<p>Dovrai implementare un'app TODO con tutte le operazioni CRUD:</p>
|
||||
<ol>
|
||||
<li><strong>Ricerca:</strong> Seleziona un utente dalla barra di ricerca</li>
|
||||
<li><strong>CREATE:</strong> Aggiungi nuovi todo per l'utente selezionato</li>
|
||||
<li><strong>READ:</strong> Visualizza i todo dell'utente</li>
|
||||
<li><strong>UPDATE:</strong> Segna come completato</li>
|
||||
<li><strong>DELETE:</strong> Elimina todo</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- Barra di ricerca utenti -->
|
||||
<div class="search-section">
|
||||
<h3>👤 Seleziona Utente</h3>
|
||||
<div class="search-container">
|
||||
<input
|
||||
type="text"
|
||||
id="userSearch"
|
||||
placeholder="Cerca un utente per nome..."
|
||||
autocomplete="off"
|
||||
>
|
||||
</div>
|
||||
<div class="suggestions" id="suggestions"></div>
|
||||
<div id="selectedUser" class="selected-user">Nessun utente selezionato</div>
|
||||
</div>
|
||||
|
||||
<!-- Input per aggiungere todo -->
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
id="todoInput"
|
||||
placeholder="Aggiungi un nuovo todo..."
|
||||
onkeypress="if(event.key === 'Enter') addTodo()"
|
||||
>
|
||||
<button class="btn" onclick="addTodo()">Aggiungi</button>
|
||||
</div>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Caricamento...</p>
|
||||
</div>
|
||||
|
||||
<div class="todos-container" id="todosContainer">
|
||||
<!-- I todo verranno inseriti qui -->
|
||||
</div>
|
||||
|
||||
<a href="../index.html" class="back-button">← Torna al Hub</a>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
158
JS_Esercizi 11 - API/todo_app/script.js
Normal file
158
JS_Esercizi 11 - API/todo_app/script.js
Normal file
@@ -0,0 +1,158 @@
|
||||
let currentUserId = null;
|
||||
|
||||
// 1. Funzione che crea un todo (oggetto)
|
||||
function createTodo(id, title, completato) {
|
||||
return { id, title, completato };
|
||||
}
|
||||
|
||||
// 2. Carica tutti i todos di un particolare utente
|
||||
async function loadUserTodos(userId) {
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('todosContainer');
|
||||
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/todos?userId=${userId}`);
|
||||
const todos = await response.json();
|
||||
displayTodos(todos);
|
||||
} catch (error) {
|
||||
container.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Aggiungi un todo
|
||||
async function addTodo() {
|
||||
if (!currentUserId) {
|
||||
alert('Seleziona un utente prima!');
|
||||
return;
|
||||
}
|
||||
|
||||
const input = document.getElementById('todoInput');
|
||||
const title = input.value.trim();
|
||||
|
||||
if (!title) {
|
||||
alert('Inserisci un todo!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/todos', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ userId: currentUserId, title, completato: false })
|
||||
});
|
||||
input.value = '';
|
||||
loadUserTodos(currentUserId);
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Toggle completato
|
||||
async function toggleTodo(id, currentCompleted) {
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/todos/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ completato: !currentCompleted })
|
||||
});
|
||||
loadUserTodos(currentUserId);
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Rimuovi un todo
|
||||
async function deleteTodo(id) {
|
||||
if (!confirm('Sei sicuro di voler eliminare?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:3000/api/todos/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
loadUserTodos(currentUserId);
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Visualizza i todos
|
||||
function displayTodos(todos) {
|
||||
const container = document.getElementById('todosContainer');
|
||||
|
||||
if (!Array.isArray(todos) || todos.length === 0) {
|
||||
container.innerHTML = '<div class="empty">Nessun todo</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const todosHTML = todos.map(todo => `
|
||||
<div class="todo-item ${todo.completato ? 'completed' : ''}">
|
||||
<div class="todo-content">
|
||||
<div class="todo-text">${todo.title}</div>
|
||||
<div class="todo-id">ID: ${todo.id}</div>
|
||||
</div>
|
||||
<div class="todo-actions">
|
||||
<button class="btn btn-small btn-complete" onclick="toggleTodo(${todo.id}, ${todo.completato})">
|
||||
${todo.completato ? '↩️' : '✓'}
|
||||
</button>
|
||||
<button class="btn btn-small btn-delete" onclick="deleteTodo(${todo.id})">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
container.innerHTML = todosHTML;
|
||||
}
|
||||
|
||||
// Carica gli utenti nella ricerca
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/users');
|
||||
const users = await response.json();
|
||||
|
||||
const searchInput = document.getElementById('userSearch');
|
||||
const suggestionsContainer = document.getElementById('suggestions');
|
||||
|
||||
// Filtra gli utenti mentre digiti
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
if (searchTerm.length === 0) {
|
||||
suggestionsContainer.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = users.filter(user =>
|
||||
user.nome.toLowerCase().includes(searchTerm) ||
|
||||
user.cognome.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
|
||||
suggestionsContainer.innerHTML = filtered.map(user => `
|
||||
<div class="suggestion-item" onclick="selectUser(${user.id}, '${user.nome} ${user.cognome}')">
|
||||
${user.nome} ${user.cognome} (${user.email})
|
||||
</div>
|
||||
`).join('');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Errore nel caricamento utenti:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Seleziona un utente
|
||||
function selectUser(userId, userName) {
|
||||
currentUserId = userId;
|
||||
document.getElementById('userSearch').value = userName;
|
||||
document.getElementById('suggestions').innerHTML = '';
|
||||
document.getElementById('selectedUser').textContent = `Utente: ${userName}`;
|
||||
loadUserTodos(userId);
|
||||
}
|
||||
|
||||
// Carica gli utenti all'avvio
|
||||
loadUsers();
|
||||
316
JS_Esercizi 11 - API/todo_app/styles.css
Normal file
316
JS_Esercizi 11 - API/todo_app/styles.css
Normal file
@@ -0,0 +1,316 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #2196F3;
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.instructions h3 {
|
||||
color: #1565c0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions ol {
|
||||
margin-left: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.instructions li {
|
||||
margin: 8px 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 20px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #764ba2;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background: #f44336;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #d32f2f;
|
||||
}
|
||||
|
||||
.btn-complete {
|
||||
background: #4caf50;
|
||||
}
|
||||
|
||||
.btn-complete:hover {
|
||||
background: #388e3c;
|
||||
}
|
||||
|
||||
.todos-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 15px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.todo-item:hover {
|
||||
background: #f5f5f5;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.todo-item.completed {
|
||||
opacity: 0.6;
|
||||
border-left-color: #4caf50;
|
||||
}
|
||||
|
||||
.todo-item.completed .todo-text {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.todo-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo-text {
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.todo-id {
|
||||
color: #999;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.todo-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #ffebee;
|
||||
border-left: 4px solid #f44336;
|
||||
color: #c62828;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: #e8f5e9;
|
||||
border-left: 4px solid #4caf50;
|
||||
color: #2e7d32;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
color: #667eea;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 10px 20px;
|
||||
background: #999;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #777;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
.counter {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
background: #f0f4ff;
|
||||
border: 2px solid #667eea;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.search-section h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#userSearch {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
#userSearch:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.suggestions:not(:empty) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.suggestion-item {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.suggestion-item:hover {
|
||||
background: #f0f4ff;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.suggestion-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.selected-user {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
padding: 10px 0;
|
||||
}
|
||||
187
JS_Esercizi 11 - API/tutorial/index.html
Normal file
187
JS_Esercizi 11 - API/tutorial/index.html
Normal file
@@ -0,0 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tutorial - API REST e Fetch</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📡 Tutorial - API REST e Fetch</h1>
|
||||
<p class="subtitle">Impara a comunicare con le API REST</p>
|
||||
|
||||
<h2>Cos'è un'API REST?</h2>
|
||||
<p>
|
||||
Un'API REST (Representational State Transfer) è un insieme di regole che permette a due applicazioni di comunicare.
|
||||
Attraverso una API REST puoi <strong>recuperare dati</strong>, <strong>crearli</strong>, <strong>modificarli</strong> e <strong>eliminarli</strong>
|
||||
da un server usando richieste HTTP.
|
||||
</p>
|
||||
|
||||
<h2>Metodi HTTP Principali</h2>
|
||||
<div class="endpoint-list">
|
||||
<div class="endpoint-item">
|
||||
<strong>GET</strong> - Recupera dati dal server
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>POST</strong> - Crea nuovi dati sul server
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>PUT</strong> - Aggiorna completamente un dato
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>PATCH</strong> - Aggiorna parzialmente un dato
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>DELETE</strong> - Elimina un dato dal server
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>L'API che Useremo</h2>
|
||||
<p>
|
||||
Utilizzeremo un server JSON locale accessibile a <code>http://localhost:3000/api</code>
|
||||
che contiene risorse diverse (utenti, todo, ecc.).
|
||||
</p>
|
||||
|
||||
<div class="note">
|
||||
<strong>⚠️ Importante:</strong> Prima di iniziare gli esercizi, assicurati che il server sia avviato!
|
||||
Apri il terminale nella cartella <code>server-api</code> e digita: <code>npm start</code>
|
||||
</div>
|
||||
|
||||
<h2>1. Fetch API - GET Request</h2>
|
||||
<p>La fetch API è il modo moderno per fare richieste HTTP in JavaScript.</p>
|
||||
|
||||
<h3>Sintassi Base:</h3>
|
||||
<div class="code-block">
|
||||
<code>fetch('https://api.example.com/data')
|
||||
.then(response => response.json())
|
||||
.then(data => console.log(data))
|
||||
.catch(error => console.error('Errore:', error));</code>
|
||||
</div>
|
||||
|
||||
<h3>Come Funziona:</h3>
|
||||
<ol>
|
||||
<li><strong>fetch(url)</strong> - Invia la richiesta al server</li>
|
||||
<li><strong>.then(response => response.json())</strong> - Converte la risposta in JSON</li>
|
||||
<li><strong>.then(data => ...)</strong> - Ricevi e usa i dati</li>
|
||||
<li><strong>.catch(error => ...)</strong> - Gestisci gli errori</li>
|
||||
</ol>
|
||||
|
||||
<h3>Esempio Pratico:</h3>
|
||||
<div class="code-block">
|
||||
<code>fetch('http://localhost:3000/api/users')
|
||||
.then(response => response.json())
|
||||
.then(users => {
|
||||
console.log('Utenti ricevuti:', users);
|
||||
users.forEach(user => {
|
||||
console.log(`${user.name} - ${user.email}`);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Errore:', error));</code>
|
||||
</div>
|
||||
|
||||
<h2>2. Fetch API - POST Request</h2>
|
||||
<p>Per inviare dati al server, usiamo il metodo POST.</p>
|
||||
|
||||
<h3>Sintassi:</h3>
|
||||
<div class="code-block">
|
||||
<code>fetch('https://api.example.com/data', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Mario',
|
||||
email: 'mario@example.com'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Creato:', data))
|
||||
.catch(error => console.error('Errore:', error));</code>
|
||||
</div>
|
||||
|
||||
<h2>3. Fetch API - DELETE Request</h2>
|
||||
<p>Per eliminare un dato dal server.</p>
|
||||
|
||||
<div class="code-block">
|
||||
<code>fetch('https://api.example.com/users/1', {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Eliminato:', data))
|
||||
.catch(error => console.error('Errore:', error));</code>
|
||||
</div>
|
||||
|
||||
<h2>4. Async/Await - Sintassi Moderna</h2>
|
||||
<p>Una sintassi più leggibile e facile da capire rispetto alle Promise.</p>
|
||||
|
||||
<h3>Sintassi:</h3>
|
||||
<div class="code-block">
|
||||
<code>async function fetchUsers() {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/users');
|
||||
const users = await response.json();
|
||||
console.log('Utenti:', users);
|
||||
} catch (error) {
|
||||
console.error('Errore:', error);
|
||||
}
|
||||
}
|
||||
|
||||
fetchUsers();</code>
|
||||
</div>
|
||||
|
||||
<div class="success">
|
||||
<strong>✓ Vantaggi:</strong> Il codice è più leggibile e simile al codice sincrono tradizionale.
|
||||
</div>
|
||||
|
||||
<h2>5. Gestione degli Errori</h2>
|
||||
<p>È importante gestire gli errori quando facciamo richieste HTTP.</p>
|
||||
|
||||
<div class="code-block">
|
||||
<code>fetch('http://localhost:3000/api/users')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Errore HTTP: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => console.log(data))
|
||||
.catch(error => {
|
||||
console.error('Errore nella richiesta:', error.message);
|
||||
});</code>
|
||||
</div>
|
||||
|
||||
<h2>6. Endpoint Disponibili sul Nostro Server</h2>
|
||||
<div class="endpoint-list">
|
||||
<div class="endpoint-item">
|
||||
<strong>GET /api/users</strong> - Ottieni lista utenti<br>
|
||||
<small>Esempio: http://localhost:3000/api/users</small>
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>GET /api/users/:id</strong> - Ottieni un utente<br>
|
||||
<small>Esempio: http://localhost:3000/api/users/1</small>
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>POST /api/users</strong> - Crea nuovo utente<br>
|
||||
<small>Body: { "name": "Mario", "email": "mario@example.com" }</small>
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>PUT /api/users/:id</strong> - Aggiorna utente<br>
|
||||
<small>Body: { "name": "Luigi", "email": "luigi@example.com" }</small>
|
||||
</div>
|
||||
<div class="endpoint-item">
|
||||
<strong>DELETE /api/users/:id</strong> - Elimina utente<br>
|
||||
<small>Esempio: http://localhost:3000/api/users/1</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Ora Sei Pronto!</h2>
|
||||
<p>
|
||||
Hai imparato i concetti fondamentali delle API REST e della Fetch API.
|
||||
Ora puoi procedere agli esercizi e metterli in pratica!
|
||||
</p>
|
||||
|
||||
<a href="../index.html" class="back-button">← Torna al Hub</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
208
JS_Esercizi 11 - API/tutorial/styles.css
Normal file
208
JS_Esercizi 11 - API/tutorial/styles.css
Normal file
@@ -0,0 +1,208 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 40px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #667eea;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 3px solid #667eea;
|
||||
padding-bottom: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #555;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #f5f5f5;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
display: block;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.example-section {
|
||||
background: #e3f2fd;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 5px solid #2196F3;
|
||||
}
|
||||
|
||||
.example-section h4 {
|
||||
color: #1565c0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.endpoint-list {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.endpoint-item {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-left: 4px solid #667eea;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.endpoint-item strong {
|
||||
color: #667eea;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 20px;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 12px 30px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: #764ba2;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.note {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.note strong {
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.success {
|
||||
background: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.success strong {
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.interactive-demo {
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.interactive-demo input,
|
||||
.interactive-demo button {
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.interactive-demo button {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.interactive-demo button:hover {
|
||||
background: #764ba2;
|
||||
}
|
||||
|
||||
.result {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
margin-top: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.85rem;
|
||||
font-family: monospace;
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
<h1>Tutorial: Manipolazione Dati</h1>
|
||||
<button id="btn-esegui">▶️ Esegui Codice</button>
|
||||
<button id="btn-reset">🧹 Pulisci Console</button>
|
||||
<a href="../index.html" style="position: absolute; top: 20px; left: 20px; text-decoration: none; color: #555; font-weight: bold;">← Dashboard</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Esercizio 1 - GET Singolo Utente</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>🔍 GET Singolo Utente</h1>
|
||||
<p class="subtitle">Recupera un utente dal server</p>
|
||||
|
||||
<!-- SEZIONE CONFIGURAZIONE -->
|
||||
<div class="config-box">
|
||||
<label for="userId">ID Utente (1-40):</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="userId" min="1" max="40" value="1">
|
||||
<button id="btnFetch">Carica</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOADING SPINNER -->
|
||||
<div id="loading" class="loading nascosto">
|
||||
⏳ Caricamento...
|
||||
</div>
|
||||
|
||||
<!-- RISULTATO -->
|
||||
<div id="result" class="result-container"></div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
102
javascript/JS_Esercizi 11 - API/01_get_singolo_utente/script.js
Normal file
102
javascript/JS_Esercizi 11 - API/01_get_singolo_utente/script.js
Normal file
@@ -0,0 +1,102 @@
|
||||
// ⚠️ COMPILARE PRIMA DI INIZIARE
|
||||
// Inserisci l'URL del server corretto
|
||||
const BASE_URL = 'http://192.168.1.7:3000/api';
|
||||
|
||||
// ELEMENTI DEL DOM (controlla che siano corretti)
|
||||
const userId = document.querySelector('#userId');
|
||||
const loading = document.querySelector('#loading');
|
||||
const result = document.querySelector('#result');
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE: Crea utente card
|
||||
*
|
||||
* Crea la card completa dell'utente e la inserisce nell'elemento result
|
||||
* L'oggetto user ha questa struttura:
|
||||
* {
|
||||
* id: number,
|
||||
* nome: string,
|
||||
* cognome: string,
|
||||
* email: string,
|
||||
* avatar: string (url),
|
||||
* dataNascita: string (formato YYYY-MM-DD),
|
||||
* comune: string,
|
||||
* attivo: boolean
|
||||
* }
|
||||
*/
|
||||
function createUserCard(user) {
|
||||
result.innerHTML = `
|
||||
<div class="user-card">
|
||||
<div class="card-header">
|
||||
<img src="${user.avatar}" alt="Avatar" class="avatar">
|
||||
<div class="user-info">
|
||||
<h2>${user.nome} ${user.cognome}</h2>
|
||||
<p class="email">${user.email}</p>
|
||||
<p class="comune">${user.comune}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail">
|
||||
<strong>Data Nascita:</strong>
|
||||
<span>${user.dataNascita}</span>
|
||||
</div>
|
||||
<div class="detail">
|
||||
<strong class="status ${user.attivo ? 'attivo' : 'inattivo'}">
|
||||
Status:
|
||||
</strong>
|
||||
<span>${user.attivo ? 'Attivo' : 'Inattivo'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE: Gestione errori
|
||||
*
|
||||
* Mostra un messaggio di errore nell'elemento result
|
||||
* e logga l'errore in console (per debug)
|
||||
*/
|
||||
function handleError(message) {
|
||||
result.innerHTML = '';
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.className = 'error';
|
||||
|
||||
let strong = document.createElement('strong');
|
||||
strong.textContent = `❌ ${message}`;
|
||||
|
||||
div.appendChild(strong);
|
||||
result.appendChild(div);
|
||||
console.error('Errore:', message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* FUNZIONE: Fetch singolo utente
|
||||
*
|
||||
* Questa funzione deve recuperare l'ID utente dall'input,
|
||||
* fare una chiamata GET a BASE_URL + "/users/" + id e mostrare i dati
|
||||
*
|
||||
* Passi:
|
||||
* 1. Leggi l'ID utente dall'input
|
||||
* 2. Controlla che l'ID sia valido, ovvero un numero tra 1 e 40
|
||||
* In caso contrario, mostra un messaggio di errore (usa handleError()) e return
|
||||
* 3. Mostra lo spinner di caricamento (rimuovi la classe nascosto)
|
||||
* 4. Fai una fetch GET a /users/{id}
|
||||
* 5. Se la risposta non è OK, usa handleError() per mostrare un messaggio e return
|
||||
* 6. Converti la risposta in JSON
|
||||
* 7. Mostra i dati dell'utente chiamando createUserCard(user)
|
||||
* 8. Nascondi lo spinner di caricamento (aggiungi la classe nascosto)
|
||||
*/
|
||||
async function fetchUser() {
|
||||
// PLACEHOLDER - Rimuovi questa riga quando completi
|
||||
handleError('Codice non implementato - Completa la funzione fetchUser()');
|
||||
}
|
||||
|
||||
|
||||
// COLLEGA IL BOTTONE AL CLICK
|
||||
document.getElementById('btnFetch').addEventListener('click', fetchUser);
|
||||
|
||||
264
javascript/JS_Esercizi 11 - API/01_get_singolo_utente/style.css
Normal file
264
javascript/JS_Esercizi 11 - API/01_get_singolo_utente/style.css
Normal file
@@ -0,0 +1,264 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.config-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.config-box label {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.input-group button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.input-group button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1.1rem;
|
||||
padding: 30px;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.result-container {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #c00;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid white;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-info h2 {
|
||||
font-size: 1.3em;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.user-info p {
|
||||
margin: 3px 0;
|
||||
opacity: 0.95;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.detail {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.detail:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.detail span {
|
||||
color: #666;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.status.attivo {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.inattivo {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.instructions-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85em;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nascosto {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Esercizio 2 - GET Lista Utenti</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>👥 GET Lista Utenti</h1>
|
||||
<p class="subtitle">Crea card dinamiche da un array</p>
|
||||
|
||||
<!-- BOTTONE CARICA -->
|
||||
<div style="text-align: center; margin-bottom: 25px;">
|
||||
<button id="btnLoadUsers" style="padding: 10px 25px; background: #007bff; color: white; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 15px;">Carica Utenti</button>
|
||||
</div>
|
||||
|
||||
<!-- LOADING SPINNER -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
⏳ Caricamento...
|
||||
</div>
|
||||
|
||||
<!-- COUNTER -->
|
||||
<div id="counter" style="display: none; text-align: center; color: #666; margin-bottom: 20px; font-weight: bold;"></div>
|
||||
|
||||
<!-- GRIGLIA UTENTI -->
|
||||
<div id="usersContainer" class="users-grid"></div>
|
||||
|
||||
<!-- ISTRUZIONI -->
|
||||
<div class="instructions-box" style="margin-top: 30px;">
|
||||
<h2>📝 Istruzioni</h2>
|
||||
<ol>
|
||||
<li>Apri <code>script.js</code> e completa <code>fetchAllUsers()</code></li>
|
||||
<li>Compila <code>BASE_URL</code></li>
|
||||
<li>GET a <code>/users</code> e usa <code>map()</code> per creare card</li>
|
||||
</ol>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 Suggerimento:</strong>
|
||||
<pre>const html = users.map(user => `<div>...</div>`).join('');</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,79 @@
|
||||
// ⚠️ COMPILARE PRIMA DI INIZIARE
|
||||
const BASE_URL = 'http://localhost:3000/api';
|
||||
|
||||
/**
|
||||
* ESERCIZIO 2: Recupera TUTTI gli utenti
|
||||
*
|
||||
* Devi completare questa funzione:
|
||||
* 1. Fai una fetch GET a: BASE_URL + '/users'
|
||||
* 2. Converti in JSON
|
||||
* 3. Chiama displayUsers() passando l'array
|
||||
* 4. Gestisci gli errori
|
||||
*/
|
||||
async function fetchAllUsers() {
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('usersContainer');
|
||||
const counter = document.getElementById('counter');
|
||||
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
counter.style.display = 'none';
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI IL TUO CODICE 👇
|
||||
// const response = await fetch(...);
|
||||
// const users = await response.json();
|
||||
// displayUsers(users);
|
||||
|
||||
// PLACEHOLDER - Rimuovi questa riga quando completi
|
||||
throw new Error('Codice non implementato - Completa la funzione fetchAllUsers()');
|
||||
|
||||
} catch (error) {
|
||||
container.innerHTML = `
|
||||
<div class="error">
|
||||
<strong>❌ Errore:</strong> ${error.message}
|
||||
</div>
|
||||
`;
|
||||
console.error('Errore:', error);
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualizza gli utenti in una griglia di card
|
||||
* (Questa funzione è già fatta - non modificare)
|
||||
*/
|
||||
function displayUsers(users) {
|
||||
const container = document.getElementById('usersContainer');
|
||||
const counter = document.getElementById('counter');
|
||||
|
||||
if (!Array.isArray(users) || users.length === 0) {
|
||||
container.innerHTML = '<div class="error">❌ Nessun utente trovato</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// CREA CARD PER OGNI UTENTE
|
||||
const cardsHTML = users.map(user => `
|
||||
<div class="user-card">
|
||||
<img src="${user.avatar || 'https://placehold.co/100'}" alt="Avatar" class="card-avatar">
|
||||
<div class="card-content">
|
||||
<h3>${user.nome} ${user.cognome}</h3>
|
||||
<p class="email">📧 ${user.email}</p>
|
||||
<p class="location">📍 ${user.comune}</p>
|
||||
<div class="status ${user.attivo ? 'attivo' : 'inattivo'}">
|
||||
${user.attivo ? '🟢 Attivo' : '🔴 Inattivo'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
container.innerHTML = cardsHTML;
|
||||
|
||||
// MOSTRA CONTATORE
|
||||
counter.innerHTML = `📊 Totale: <strong>${users.length} utenti</strong>`;
|
||||
counter.style.display = 'block';
|
||||
}
|
||||
|
||||
// COLLEGA IL BOTTONE
|
||||
document.getElementById('btnLoadUsers').addEventListener('click', fetchAllUsers);
|
||||
189
javascript/JS_Esercizi 11 - API/02_get_lista_utenti/style.css
Normal file
189
javascript/JS_Esercizi 11 - API/02_get_lista_utenti/style.css
Normal file
@@ -0,0 +1,189 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
padding: 30px;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.users-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.user-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.card-avatar {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card-content h3 {
|
||||
font-size: 0.95rem;
|
||||
color: #333;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.card-content p {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.email {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.status.attivo {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.inattivo {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #c00;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.instructions-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8em;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
62
javascript/JS_Esercizi 11 - API/03_utente_e_post/index.html
Normal file
62
javascript/JS_Esercizi 11 - API/03_utente_e_post/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Esercizio 3 - Utente + Post</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>📝 Utente + Post</h1>
|
||||
<p class="subtitle">Fetch multipli e relazioni dati</p>
|
||||
|
||||
<!-- SEZIONE CONFIGURAZIONE -->
|
||||
<div class="config-box">
|
||||
<label for="userId">ID Utente (1-40):</label>
|
||||
<div class="input-group">
|
||||
<input type="number" id="userId" min="1" max="40" value="1">
|
||||
<button id="btnFetch">Carica</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOADING -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
⏳ Caricamento...
|
||||
</div>
|
||||
|
||||
<!-- PROFILO UTENTE -->
|
||||
<div id="userProfile" class="user-profile"></div>
|
||||
|
||||
<!-- POST -->
|
||||
<div id="postsContainer" class="posts-container"></div>
|
||||
|
||||
<!-- ISTRUZIONI -->
|
||||
<div class="instructions-box" style="margin-top: 30px;">
|
||||
<h2>📝 Istruzioni</h2>
|
||||
<ol>
|
||||
<li>Apri <code>script.js</code> e completa <code>fetchUserAndPosts()</code></li>
|
||||
<li>Compila <code>BASE_URL</code></li>
|
||||
<li>Fai due fetch: <code>/users/{id}</code> e <code>/posts</code></li>
|
||||
<li>Usa <code>filter()</code> per relazionare i dati</li>
|
||||
</ol>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 Suggerimento:</strong>
|
||||
<pre>const user = await fetch(...);
|
||||
const allPosts = await fetch(...);
|
||||
const userPosts = allPosts.filter(post => post.userId === user.id);</pre>
|
||||
</div>
|
||||
|
||||
<div class="challenge">
|
||||
<strong>🎯 Bonus Challenge:</strong>
|
||||
<p>Ordina i post dal più recente al meno recente usando <code>sort()</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
124
javascript/JS_Esercizi 11 - API/03_utente_e_post/script.js
Normal file
124
javascript/JS_Esercizi 11 - API/03_utente_e_post/script.js
Normal file
@@ -0,0 +1,124 @@
|
||||
// ⚠️ COMPILARE PRIMA DI INIZIARE
|
||||
const BASE_URL = 'http://localhost:3000/api';
|
||||
|
||||
/**
|
||||
* ESERCIZIO 3: Recupera un utente E tutti i suoi post
|
||||
*
|
||||
* Devi completare questa funzione:
|
||||
* 1. Leggi l'ID dell'utente
|
||||
* 2. Fai DUE fetch:
|
||||
* - GET /users/{id}
|
||||
* - GET /posts
|
||||
* 3. Filtra i post per trovare solo quelli di questo utente (usando userId)
|
||||
* 4. Mostra i risultati
|
||||
* 5. Gestisci gli errori
|
||||
*/
|
||||
async function fetchUserAndPosts() {
|
||||
const userId = document.getElementById('userId').value;
|
||||
const loading = document.getElementById('loading');
|
||||
const userProfile = document.getElementById('userProfile');
|
||||
const postsContainer = document.getElementById('postsContainer');
|
||||
|
||||
if (!userId || userId < 1 || userId > 40) {
|
||||
userProfile.innerHTML = '<div class="error">❌ Inserisci un ID valido tra 1 e 40</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
loading.style.display = 'block';
|
||||
userProfile.innerHTML = '';
|
||||
postsContainer.innerHTML = '';
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI IL TUO CODICE 👇
|
||||
// 1. Fetch utente
|
||||
// const userResponse = await fetch(...);
|
||||
// const user = await userResponse.json();
|
||||
|
||||
// 2. Fetch tutti i post
|
||||
// const postsResponse = await fetch(...);
|
||||
// const allPosts = await postsResponse.json();
|
||||
|
||||
// 3. Filtra i post di questo utente
|
||||
// const userPosts = allPosts.filter(post => post.userId === user.id);
|
||||
|
||||
// 4. Visualizza
|
||||
// displayUserWithPosts(user, userPosts);
|
||||
|
||||
// PLACEHOLDER - Rimuovi questa riga quando completi
|
||||
throw new Error('Codice non implementato - Completa la funzione fetchUserAndPosts()');
|
||||
|
||||
} catch (error) {
|
||||
userProfile.innerHTML = `
|
||||
<div class="error">
|
||||
<strong>❌ Errore:</strong> ${error.message}
|
||||
</div>
|
||||
`;
|
||||
console.error('Errore:', error);
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualizza utente e post
|
||||
* (Questa funzione è già fatta - non modificare)
|
||||
*/
|
||||
function displayUserWithPosts(user, posts) {
|
||||
const userProfile = document.getElementById('userProfile');
|
||||
const postsContainer = document.getElementById('postsContainer');
|
||||
|
||||
// CARD UTENTE
|
||||
const userHTML = `
|
||||
<div class="user-card">
|
||||
<div class="card-header">
|
||||
<img src="${user.avatar}" alt="Avatar" class="avatar">
|
||||
<div class="user-info">
|
||||
<h2>${user.nome} ${user.cognome}</h2>
|
||||
<p class="email">📧 ${user.email}</p>
|
||||
<p class="location">📍 ${user.comune}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
userProfile.innerHTML = userHTML;
|
||||
|
||||
// POST DELL'UTENTE
|
||||
if (!posts || posts.length === 0) {
|
||||
postsContainer.innerHTML = `
|
||||
<div class="posts-section">
|
||||
<h2>📄 Post (0)</h2>
|
||||
<div class="empty">Questo utente non ha scritto nessun post</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const postsHTML = `
|
||||
<div class="posts-section">
|
||||
<h2>📄 Post (${posts.length})</h2>
|
||||
${posts.map(post => `
|
||||
<div class="post-card">
|
||||
<div class="post-header">
|
||||
<h3>${post.titolo}</h3>
|
||||
<span class="post-date">📅 ${post.data}</span>
|
||||
</div>
|
||||
<p class="post-content">${post.contenuto}</p>
|
||||
<div class="post-footer">
|
||||
<span class="likes">❤️ ${post.likes} likes</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
postsContainer.innerHTML = postsHTML;
|
||||
}
|
||||
|
||||
// COLLEGA IL BOTTONE
|
||||
document.getElementById('btnFetch').addEventListener('click', fetchUserAndPosts);
|
||||
|
||||
// PERMETTI ENTER
|
||||
document.getElementById('userId').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') fetchUserAndPosts();
|
||||
});
|
||||
249
javascript/JS_Esercizi 11 - API/03_utente_e_post/style.css
Normal file
249
javascript/JS_Esercizi 11 - API/03_utente_e_post/style.css
Normal file
@@ -0,0 +1,249 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.config-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.config-box label {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.2);
|
||||
}
|
||||
|
||||
.input-group button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.input-group button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
padding: 30px;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #c00;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-info h2 {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.user-info p {
|
||||
margin: 3px 0;
|
||||
opacity: 0.95;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.posts-section h2 {
|
||||
color: #333;
|
||||
margin: 25px 0 15px 0;
|
||||
font-size: 1.2rem;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.post-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.post-header h3 {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.post-date {
|
||||
color: #999;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
color: #555;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.post-footer {
|
||||
color: #d63384;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.instructions-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85em;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
}
|
||||
93
javascript/JS_Esercizi 11 - API/04_todo_app_crud/index.html
Normal file
93
javascript/JS_Esercizi 11 - API/04_todo_app_crud/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>Esercizio 4 - Todo App CRUD</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>✅ Todo App CRUD</h1>
|
||||
<p class="subtitle">GET, POST, PUT, DELETE - App completa</p>
|
||||
|
||||
<!-- SEZIONE SELEZIONE UTENTE -->
|
||||
<div class="config-box">
|
||||
<h2>👤 Seleziona Utente</h2>
|
||||
<div class="input-group">
|
||||
<input type="number" id="userId" min="1" max="40" value="1" placeholder="ID Utente (1-40)">
|
||||
<button id="btnLoadTodos">Carica TODO</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SEZIONE AGGIUNTA TODO -->
|
||||
<div id="addTodoSection" class="add-todo-box" style="display: none;">
|
||||
<h2>➕ Aggiungi Nuovo TODO</h2>
|
||||
<div class="input-group">
|
||||
<input type="text" id="todoInput" placeholder="Scrivi un nuovo TODO...">
|
||||
<button id="btnAddTodo">Aggiungi</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOADING -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
⏳ Caricamento...
|
||||
</div>
|
||||
|
||||
<!-- COUNTER -->
|
||||
<div id="counter" class="counter" style="display: none;"></div>
|
||||
|
||||
<!-- LISTA TODO -->
|
||||
<div id="todosContainer" class="todos-container"></div>
|
||||
|
||||
<!-- ISTRUZIONI -->
|
||||
<div class="instructions">
|
||||
<h2>📝 Cosa Devi Fare</h2>
|
||||
<p style="margin-bottom: 15px;"><strong>Questa è l'esercitazione finale!</strong> Devi implementare TUTTE le operazioni CRUD:</p>
|
||||
|
||||
<h3>1️⃣ Carica TODO (GET)</h3>
|
||||
<ul style="margin-left: 20px; margin-bottom: 15px;">
|
||||
<li>Fai una GET a <code>/todos?userId={id}</code> per ottenere i TODO dell'utente</li>
|
||||
<li>Visualizza la lista</li>
|
||||
</ul>
|
||||
|
||||
<h3>2️⃣ Aggiungi TODO (POST)</h3>
|
||||
<ul style="margin-left: 20px; margin-bottom: 15px;">
|
||||
<li>Fai una POST a <code>/todos</code> con: <code>{userId, titolo, completato: false}</code></li>
|
||||
<li>Ricarica la lista</li>
|
||||
</ul>
|
||||
|
||||
<h3>3️⃣ Modifica TODO (PUT)</h3>
|
||||
<ul style="margin-left: 20px; margin-bottom: 15px;">
|
||||
<li>Fai una PUT a <code>/todos/{id}</code> con: <code>{completato: !currentValue}</code></li>
|
||||
<li>Aggiorna la lista</li>
|
||||
</ul>
|
||||
|
||||
<h3>4️⃣ Elimina TODO (DELETE)</h3>
|
||||
<ul style="margin-left: 20px; margin-bottom: 15px;">
|
||||
<li>Fai una DELETE a <code>/todos/{id}</code></li>
|
||||
<li>Rimuovi dalla lista</li>
|
||||
</ul>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 Struttura del codice:</strong>
|
||||
<pre>// Funzioni che devi implementare:
|
||||
loadUserTodos(userId) // GET
|
||||
addTodo() // POST
|
||||
toggleTodo(id, current) // PUT
|
||||
deleteTodo(id) // DELETE
|
||||
displayTodos(todos) // Visualizza (già fatto)</pre>
|
||||
</div>
|
||||
|
||||
<div class="challenge">
|
||||
<strong>🎯 Bonus Challenge:</strong>
|
||||
<p>Aggiungi un riepilogo: quanti TODO sono completati vs quanti rimangono?</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
197
javascript/JS_Esercizi 11 - API/04_todo_app_crud/script.js
Normal file
197
javascript/JS_Esercizi 11 - API/04_todo_app_crud/script.js
Normal file
@@ -0,0 +1,197 @@
|
||||
// ⚠️ COMPILARE PRIMA DI INIZIARE
|
||||
const BASE_URL = 'http://localhost:3000/api';
|
||||
|
||||
let currentUserId = null;
|
||||
|
||||
/**
|
||||
* ESERCIZIO 4: Todo App CRUD Completa
|
||||
*
|
||||
* Devi implementare 4 funzioni:
|
||||
* 1. loadUserTodos() - GET /todos?userId={id}
|
||||
* 2. addTodo() - POST /todos
|
||||
* 3. toggleTodo() - PUT /todos/{id}
|
||||
* 4. deleteTodo() - DELETE /todos/{id}
|
||||
*/
|
||||
|
||||
// ======== 1️⃣ CARICA TODO (GET) ========
|
||||
/**
|
||||
* Leggi l'ID utente, fai GET a /todos?userId={id}
|
||||
* Mostra la lista con displayTodos()
|
||||
*/
|
||||
async function loadUserTodos() {
|
||||
const userId = document.getElementById('userId').value;
|
||||
|
||||
if (!userId || userId < 1 || userId > 40) {
|
||||
alert('Inserisci un ID valido tra 1 e 40');
|
||||
return;
|
||||
}
|
||||
|
||||
currentUserId = userId;
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('todosContainer');
|
||||
const addSection = document.getElementById('addTodoSection');
|
||||
const counter = document.getElementById('counter');
|
||||
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
counter.style.display = 'none';
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI - Fai fetch GET a /todos con query parameter userId
|
||||
// const response = await fetch(BASE_URL + '/todos?userId=' + userId);
|
||||
// const todos = await response.json();
|
||||
// displayTodos(todos);
|
||||
|
||||
throw new Error('Codice non implementato - Completa loadUserTodos()');
|
||||
|
||||
} catch (error) {
|
||||
container.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
console.error('Errore:', error);
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
addSection.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// ======== 2️⃣ AGGIUNGI TODO (POST) ========
|
||||
/**
|
||||
* Leggi il testo dall'input
|
||||
* Fai POST a /todos con {userId, titolo, completato: false}
|
||||
* Ricarica la lista
|
||||
*/
|
||||
async function addTodo() {
|
||||
if (!currentUserId) {
|
||||
alert('Carica prima una lista di TODO!');
|
||||
return;
|
||||
}
|
||||
|
||||
const input = document.getElementById('todoInput');
|
||||
const titolo = input.value.trim();
|
||||
|
||||
if (!titolo) {
|
||||
alert('Scrivi un TODO!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI - Fai fetch POST
|
||||
// const response = await fetch(BASE_URL + '/todos', {
|
||||
// method: 'POST',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ userId: currentUserId, titolo, completato: false })
|
||||
// });
|
||||
// input.value = '';
|
||||
// loadUserTodos();
|
||||
|
||||
throw new Error('Codice non implementato - Completa addTodo()');
|
||||
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
console.error('Errore:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ======== 3️⃣ MODIFICA TODO (PUT) ========
|
||||
/**
|
||||
* Fai PUT a /todos/{id} con {completato: !currentValue}
|
||||
* Ricarica la lista
|
||||
*/
|
||||
async function toggleTodo(id, currentCompleted) {
|
||||
try {
|
||||
// 👇 SCRIVI QUI - Fai fetch PUT
|
||||
// const response = await fetch(BASE_URL + '/todos/' + id, {
|
||||
// method: 'PUT',
|
||||
// headers: { 'Content-Type': 'application/json' },
|
||||
// body: JSON.stringify({ completato: !currentCompleted })
|
||||
// });
|
||||
// loadUserTodos();
|
||||
|
||||
throw new Error('Codice non implementato - Completa toggleTodo()');
|
||||
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
console.error('Errore:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ======== 4️⃣ ELIMINA TODO (DELETE) ========
|
||||
/**
|
||||
* Chiedi conferma con confirm()
|
||||
* Fai DELETE a /todos/{id}
|
||||
* Ricarica la lista
|
||||
*/
|
||||
async function deleteTodo(id) {
|
||||
if (!confirm('Sei sicuro di voler eliminare questo TODO?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI - Fai fetch DELETE
|
||||
// const response = await fetch(BASE_URL + '/todos/' + id, {
|
||||
// method: 'DELETE'
|
||||
// });
|
||||
// loadUserTodos();
|
||||
|
||||
throw new Error('Codice non implementato - Completa deleteTodo()');
|
||||
|
||||
} catch (error) {
|
||||
alert('Errore: ' + error.message);
|
||||
console.error('Errore:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualizza i TODO
|
||||
* (Questa funzione è già fatta - non modificare)
|
||||
*/
|
||||
function displayTodos(todos) {
|
||||
const container = document.getElementById('todosContainer');
|
||||
const counter = document.getElementById('counter');
|
||||
|
||||
if (!Array.isArray(todos) || todos.length === 0) {
|
||||
container.innerHTML = '<div class="empty">Nessun TODO. Creane uno!</div>';
|
||||
counter.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// CONTA COMPLETATI E NON
|
||||
const completed = todos.filter(t => t.completato).length;
|
||||
const pending = todos.length - completed;
|
||||
|
||||
// MOSTRA COUNTER
|
||||
counter.innerHTML = `
|
||||
📊 Totale: <strong>${todos.length}</strong> |
|
||||
✅ Completati: <strong>${completed}</strong> |
|
||||
⏳ In Sospeso: <strong>${pending}</strong>
|
||||
`;
|
||||
counter.style.display = 'block';
|
||||
|
||||
// CREA CARD TODO
|
||||
const todosHTML = todos.map(todo => `
|
||||
<div class="todo-item ${todo.completato ? 'completed' : ''}">
|
||||
<div class="todo-checkbox">
|
||||
<input type="checkbox"
|
||||
${todo.completato ? 'checked' : ''}
|
||||
onchange="toggleTodo(${todo.id}, ${todo.completato})">
|
||||
</div>
|
||||
<div class="todo-content">
|
||||
<div class="todo-title">${todo.titolo}</div>
|
||||
<div class="todo-id">ID: ${todo.id}</div>
|
||||
</div>
|
||||
<button class="btn-delete" onclick="deleteTodo(${todo.id})">🗑️ Elimina</button>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
container.innerHTML = todosHTML;
|
||||
}
|
||||
|
||||
// ======== COLLEGA GLI EVENTI ========
|
||||
document.getElementById('btnLoadTodos').addEventListener('click', loadUserTodos);
|
||||
document.getElementById('btnAddTodo').addEventListener('click', addTodo);
|
||||
|
||||
// PERMETTI ENTER per aggiungere TODO
|
||||
document.getElementById('todoInput').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addTodo();
|
||||
}
|
||||
});
|
||||
266
javascript/JS_Esercizi 11 - API/04_todo_app_crud/style.css
Normal file
266
javascript/JS_Esercizi 11 - API/04_todo_app_crud/style.css
Normal file
@@ -0,0 +1,266 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.config-box, .add-todo-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.config-box label, .add-todo-box label {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.input-group button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-group button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-si#666
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.counter {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
padding: 15p#f0f0f0;
|
||||
color: #333;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
text-align: center
|
||||
.todos-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 50px;
|
||||
}0px;
|
||||
margin-bottom: 3
|
||||
.todo-item {
|
||||
background: white;
|
||||
padding: 15p#f9f9f9;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
border-left: 4px solid #007bff;
|
||||
animation: slideIn 0.2s ease-out
|
||||
|
||||
.todo-item.completed {
|
||||
background: #f5f5f5;
|
||||
border-left-color: #4caf50;
|
||||
opacity: 0.7;
|
||||
}0f0f0;
|
||||
opacity: 0.7;
|
||||
border-left-color: #28a745eted .todo-title {
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
to {1
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.todo-checkbox {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.todo-checkbox input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor:18px;
|
||||
height: 18
|
||||
|
||||
.todo-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo-title {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
font-size: 1em;
|
||||
margin-bottom: 4px;
|
||||
}margin: 0
|
||||
.todo-id {
|
||||
color: #999;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.btn-delete {rem;
|
||||
margin: 2px 0 0 0
|
||||
padding: 8px 12px;
|
||||
background: #fee;
|
||||
color: #c6px 10px;
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
border: 1px solid #fcc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: 500
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #fcc;
|
||||
color: #800;
|
||||
|
||||
.empty {
|
||||
background: white;
|
||||
color: #999;
|
||||
padding: 40p#f0f0f0;
|
||||
color: #999;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
border: 1
|
||||
.error {
|
||||
background: #fee;
|
||||
color: #c00;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #c00;
|
||||
font-weight: 506px;
|
||||
border-left: 4px solid #c
|
||||
.instructions {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 25px;
|
||||
border-ra-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box h3 {
|
||||
color: #555;
|
||||
margin: 15px 0 10px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol, .instructions-box ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8em;
|
||||
color: #333;
|
||||
margin: 0
|
||||
76
javascript/JS_Esercizi 11 - API/05_meteo/index.html
Normal file
76
javascript/JS_Esercizi 11 - API/05_meteo/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Extra 1 - App Meteo</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>🌤️ App Meteo</h1>
|
||||
<p class="subtitle">API pubblica Open-Meteo</p>
|
||||
|
||||
<!-- SEZIONE RICERCA -->
|
||||
<div class="search-box">
|
||||
<h2>🔍 Ricerca Città</h2>
|
||||
<label>Latitudine e Longitudine:</label>
|
||||
<div class="coord-group">
|
||||
<input type="number" id="latitude" placeholder="Lat" step="0.01" value="45.4642">
|
||||
<input type="number" id="longitude" placeholder="Lon" step="0.01" value="9.1900">
|
||||
<button id="btnSearch">Cerca Meteo</button>
|
||||
</div>
|
||||
<p class="hint-text">💡 Esempi: Milano (45.46, 9.19) | Roma (41.90, 12.50) | Napoli (40.85, 14.27)</p>
|
||||
</div>
|
||||
|
||||
<!-- LOADING -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
⏳ Caricamento meteo...
|
||||
</div>
|
||||
|
||||
<!-- RISULTATO -->
|
||||
<div id="weatherContainer" class="weather-container"></div>
|
||||
|
||||
<!-- ISTRUZIONI -->
|
||||
<div class="instructions">
|
||||
<h2>📝 Cosa Devi Fare</h2>
|
||||
<ol>
|
||||
<li>Leggi latitudine e longitudine dagli input</li>
|
||||
<li>Fai una GET a <code>https://api.open-meteo.com/v1/forecast</code> con parametri:
|
||||
<ul style="margin-top: 10px;">
|
||||
<li><code>latitude={lat}</code></li>
|
||||
<li><code>longitude={lon}</code></li>
|
||||
<li><code>current=temperature_2m,relative_humidity_2m,weather_code</code></li>
|
||||
<li><code>timezone=auto</code></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Estrai i dati dal JSON: <code>response.current</code></li>
|
||||
<li>Visualizza temperatura, umidità, descrizione meteo</li>
|
||||
</ol>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 URL Completo:</strong>
|
||||
<pre>https://api.open-meteo.com/v1/forecast?latitude=45.46&longitude=9.19¤t=temperature_2m,relative_humidity_2m,weather_code&timezone=auto</pre>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 Struttura Risposta:</strong>
|
||||
<pre>response.current = {
|
||||
temperature_2m: 22.5,
|
||||
relative_humidity_2m: 65,
|
||||
weather_code: 0 // 0=soleggiato, 1=nuvoloso, ecc
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="challenge">
|
||||
<strong>🎯 Bonus Challenge:</strong>
|
||||
<p>Converti il codice meteo in emoji (0=☀️, 1=⛅, 2=☁️, 3=🌧️, ecc.)</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
111
javascript/JS_Esercizi 11 - API/05_meteo/script.js
Normal file
111
javascript/JS_Esercizi 11 - API/05_meteo/script.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* EXTRA 1: App Meteo con Open-Meteo
|
||||
*
|
||||
* Open-Meteo è un'API PUBBLICA e GRATUITA che NON richiede autenticazione!
|
||||
* Puoi fare centinaia di richieste al giorno senza problemi.
|
||||
*
|
||||
* API Base: https://api.open-meteo.com/v1/forecast
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ricerca il meteo per latitudine e longitudine
|
||||
*
|
||||
* Parametri obbligatori:
|
||||
* - latitude: numero decimale
|
||||
* - longitude: numero decimale
|
||||
* - current: variabili da ottenere (separati da virgola)
|
||||
* - timezone: 'auto' oppure fuso orario specifico
|
||||
*/
|
||||
async function searchWeather() {
|
||||
const latitude = document.getElementById('latitude').value;
|
||||
const longitude = document.getElementById('longitude').value;
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('weatherContainer');
|
||||
|
||||
// VALIDAZIONE
|
||||
if (!latitude || !longitude) {
|
||||
container.innerHTML = '<div class="error">❌ Inserisci latitudine e longitudine</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI IL TUO CODICE 👇
|
||||
|
||||
// Costruisci l'URL con i parametri corretti:
|
||||
// const url = 'https://api.open-meteo.com/v1/forecast?latitude=' + ...
|
||||
// const response = await fetch(url);
|
||||
// const data = await response.json();
|
||||
// displayWeather(data.current, latitude, longitude);
|
||||
|
||||
throw new Error('Codice non implementato - Completa searchWeather()');
|
||||
|
||||
} catch (error) {
|
||||
container.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
console.error('Errore:', error);
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualizza i dati meteo
|
||||
* (Questa funzione è già fatta)
|
||||
*/
|
||||
function displayWeather(current, lat, lon) {
|
||||
const container = document.getElementById('weatherContainer');
|
||||
|
||||
// Converti codice meteo in descrizione
|
||||
const weatherDescriptions = {
|
||||
0: { emoji: '☀️', descrizione: 'Sereno' },
|
||||
1: { emoji: '🌤️', descrizione: 'Poco nuvoloso' },
|
||||
2: { emoji: '⛅', descrizione: 'Nuvoloso' },
|
||||
3: { emoji: '☁️', descrizione: 'Molto nuvoloso' },
|
||||
45: { emoji: '🌫️', descrizione: 'Nebbia' },
|
||||
48: { emoji: '🌫️', descrizione: 'Nebbia con brina' },
|
||||
51: { emoji: '🌧️', descrizione: 'Pioggia leggera' },
|
||||
53: { emoji: '🌧️', descrizione: 'Pioggia' },
|
||||
55: { emoji: '⛈️', descrizione: 'Pioggia forte' },
|
||||
80: { emoji: '🌧️', descrizione: 'Pioggia leggera' },
|
||||
81: { emoji: '🌧️', descrizione: 'Pioggia' },
|
||||
82: { emoji: '⛈️', descrizione: 'Pioggia forte' },
|
||||
95: { emoji: '⛈️', descrizione: 'Temporale' },
|
||||
};
|
||||
|
||||
const weather = weatherDescriptions[current.weather_code] || { emoji: '❓', descrizione: 'Sconosciuto' };
|
||||
|
||||
const html = `
|
||||
<div class="weather-card">
|
||||
<div class="location">
|
||||
📍 ${lat.toFixed(2)}°N, ${lon.toFixed(2)}°E
|
||||
</div>
|
||||
|
||||
<div class="weather-main">
|
||||
<div class="emoji">${weather.emoji}</div>
|
||||
<div class="temp">${current.temperature_2m}°C</div>
|
||||
</div>
|
||||
|
||||
<div class="weather-details">
|
||||
<p><strong>Condizione:</strong> ${weather.descrizione}</p>
|
||||
<p><strong>Umidità:</strong> ${current.relative_humidity_2m}%</p>
|
||||
<p><strong>Codice Meteo:</strong> ${current.weather_code}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// COLLEGA IL BOTTONE
|
||||
document.getElementById('btnSearch').addEventListener('click', searchWeather);
|
||||
|
||||
// PERMETTI ENTER
|
||||
document.getElementById('latitude').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') searchWeather();
|
||||
});
|
||||
|
||||
document.getElementById('longitude').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') searchWeather();
|
||||
});
|
||||
228
javascript/JS_Esercizi 11 - API/05_meteo/style.css
Normal file
228
javascript/JS_Esercizi 11 - API/05_meteo/style.css
Normal file
@@ -0,0 +1,228 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.search-box label {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.coord-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.coord-group input {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
border: 2p0px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.coord-group input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.coord-group button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.coord-group button:hover {
|
||||
background: #0056b3
|
||||
|
||||
.hint-text {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
margin-top: 185rpx;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-si#666;
|
||||
padding: 3 pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.weather-container {
|
||||
margin-bottom: 50px;
|
||||
}30px;
|
||||
}
|
||||
|
||||
.weather-card {
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.location {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.weather-main {
|
||||
text-align: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 4em;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.weather-main {
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
font-size: 3.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.temp {
|
||||
font-size: 2.2em;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.weather-details {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.weather-details p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
line-height: 1.5 rgba(255, 255, 255, 0.95);
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
margin-top: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.instructions h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size15px;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #c
|
||||
margin-left: 25px;
|
||||
color: #555;
|
||||
line-heig-box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol, .instructions-box ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8em;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
word-break: break-all
|
||||
75
javascript/JS_Esercizi 11 - API/06_pokedex/index.html
Normal file
75
javascript/JS_Esercizi 11 - API/06_pokedex/index.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Extra 2 - Pokédex</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>
|
||||
|
||||
<div class="app-container">
|
||||
<h1>🔴 Pokédex</h1>
|
||||
<p class="subtitle">API pubblica PokéAPI</p>
|
||||
|
||||
<!-- SEZIONE RICERCA -->
|
||||
<div class="search-box">
|
||||
<h2>🔍 Cerca Pokémon</h2>
|
||||
<label>Nome o Numero:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="pokemonInput" placeholder="Pikachu oppure 25" value="pikachu">
|
||||
<button id="btnSearch">Cerca</button>
|
||||
</div>
|
||||
<p class="hint-text">💡 Esempi: pikachu, charizard, 1 (Bulbasaur), 6 (Charizard)</p>
|
||||
</div>
|
||||
|
||||
<!-- LOADING -->
|
||||
<div id="loading" class="loading" style="display: none;">
|
||||
⏳ Cercando Pokémon...
|
||||
</div>
|
||||
|
||||
<!-- RISULTATO -->
|
||||
<div id="pokemonContainer" class="pokemon-container"></div>
|
||||
|
||||
<!-- ISTRUZIONI -->
|
||||
<div class="instructions">
|
||||
<h2>📝 Cosa Devi Fare</h2>
|
||||
<ol>
|
||||
<li>Leggi il nome/numero dal campo input</li>
|
||||
<li>Fai una GET a <code>https://pokeapi.co/api/v2/pokemon/{name_or_id}</code></li>
|
||||
<li>Se ricevi un errore 404, mostra "Pokémon non trovato"</li>
|
||||
<li>Estrai da <code>response</code>:
|
||||
<ul style="margin-top: 10px;">
|
||||
<li><code>name</code> - Nome</li>
|
||||
<li><code>sprites.front_default</code> - Immagine</li>
|
||||
<li><code>height</code> - Altezza (in decimetri)</li>
|
||||
<li><code>weight</code> - Peso (in ettogrammi)</li>
|
||||
<li><code>types[].type.name</code> - Tipi (array)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Visualizza le informazioni in una card</li>
|
||||
</ol>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 URL Completo:</strong>
|
||||
<pre>https://pokeapi.co/api/v2/pokemon/pikachu</pre>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
<strong>💡 Gestire errore 404:</strong>
|
||||
<pre>if (!response.ok) {
|
||||
throw new Error('Pokémon non trovato');
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="challenge">
|
||||
<strong>🎯 Bonus Challenge:</strong>
|
||||
<p>Colora la card in base al tipo (Fire=rosso, Water=blu, ecc.). Oppure aggiungi 5 Pokémon random al caricamento della pagina.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
115
javascript/JS_Esercizi 11 - API/06_pokedex/script.js
Normal file
115
javascript/JS_Esercizi 11 - API/06_pokedex/script.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* EXTRA 2: Pokédex con PokéAPI
|
||||
*
|
||||
* PokéAPI è un'API PUBBLICA e GRATUITA con migliaia di dati su Pokémon!
|
||||
* NON richiede autenticazione, puoi fare tante richieste quante vuoi.
|
||||
*
|
||||
* API Base: https://pokeapi.co/api/v2/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ricerca un Pokémon per nome o numero
|
||||
*
|
||||
* Parametri:
|
||||
* - Accetta nome (in minuscolo) o numero ID
|
||||
* - Ritorna oggetto con tutte le info del Pokémon
|
||||
*/
|
||||
async function searchPokemon() {
|
||||
const input = document.getElementById('pokemonInput').value.trim().toLowerCase();
|
||||
const loading = document.getElementById('loading');
|
||||
const container = document.getElementById('pokemonContainer');
|
||||
|
||||
// VALIDAZIONE
|
||||
if (!input) {
|
||||
container.innerHTML = '<div class="error">❌ Inserisci il nome o numero di un Pokémon</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
loading.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
|
||||
try {
|
||||
// 👇 SCRIVI QUI IL TUO CODICE 👇
|
||||
|
||||
// Fai una fetch GET a: https://pokeapi.co/api/v2/pokemon/ + input
|
||||
// const response = await fetch('https://pokeapi.co/api/v2/pokemon/' + input);
|
||||
|
||||
// Controlla se la risposta è OK (non 404)
|
||||
// if (!response.ok) {
|
||||
// throw new Error('Pokémon non trovato');
|
||||
// }
|
||||
|
||||
// Converti in JSON
|
||||
// const pokemon = await response.json();
|
||||
|
||||
// Visualizza
|
||||
// displayPokemon(pokemon);
|
||||
|
||||
throw new Error('Codice non implementato - Completa searchPokemon()');
|
||||
|
||||
} catch (error) {
|
||||
container.innerHTML = `<div class="error">❌ ${error.message}</div>`;
|
||||
console.error('Errore:', error);
|
||||
} finally {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualizza le informazioni del Pokémon
|
||||
* (Questa funzione è già fatta)
|
||||
*/
|
||||
function displayPokemon(pokemon) {
|
||||
const container = document.getElementById('pokemonContainer');
|
||||
|
||||
// ESTRAI I DATI
|
||||
const name = pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1);
|
||||
const id = pokemon.id;
|
||||
const image = pokemon.sprites.front_default || 'https://placehold.co/200';
|
||||
const height = (pokemon.height / 10).toFixed(1); // Converti da dm a metri
|
||||
const weight = (pokemon.weight / 10).toFixed(1); // Converti da hg a kg
|
||||
const types = pokemon.types.map(t => t.type.name).join(', ');
|
||||
|
||||
const html = `
|
||||
<div class="pokemon-card">
|
||||
<div class="card-header">
|
||||
<h2>#${id} - ${name}</h2>
|
||||
</div>
|
||||
|
||||
<div class="card-image">
|
||||
<img src="${image}" alt="${name}">
|
||||
</div>
|
||||
|
||||
<div class="card-details">
|
||||
<div class="detail-row">
|
||||
<strong>Tipo:</strong>
|
||||
<span>${types}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Altezza:</strong>
|
||||
<span>${height} m</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<strong>Peso:</strong>
|
||||
<span>${weight} kg</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<small>Dati da <a href="https://pokeapi.co/" target="_blank">PokéAPI</a></small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// COLLEGA IL BOTTONE
|
||||
document.getElementById('btnSearch').addEventListener('click', searchPokemon);
|
||||
|
||||
// PERMETTI ENTER
|
||||
document.getElementById('pokemonInput').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
searchPokemon();
|
||||
}
|
||||
});
|
||||
245
javascript/JS_Esercizi 11 - API/06_pokedex/style.css
Normal file
245
javascript/JS_Esercizi 11 - API/06_pokedex/style.css
Normal file
@@ -0,0 +1,245 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
background: white;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin: 0 0 30px 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
background: white;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.search-box h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
#f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.search-box label {
|
||||
display: block;
|
||||
color: #555;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10 #ddd;
|
||||
border-radius: 60px;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.input-group button {
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-group button:hover {
|
||||
background: #85r056b3
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 1.2em;
|
||||
padding#666;
|
||||
padding: 3
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
}
|
||||
|
||||
.pokemon-container {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.pokemon-card {30px;
|
||||
}
|
||||
|
||||
.pokemon-card {
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);-header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.card-image {
|
||||
background: #f9f9f9;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}007bff 0%, #0056b3 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.3r
|
||||
.card-details {white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.card-image img {
|
||||
max-width: 18 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}0px;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-row strong {
|
||||
color: #333solid #eee;
|
||||
}
|
||||
|
||||
.card-footer small {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}2px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 0.85rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.card-footer a {
|
||||
color: #007bff 8px;
|
||||
border-left: 4px solid #c00;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: -box {
|
||||
background: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
border-left: 4px solid #007bff;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.instructions-box h2 {
|
||||
color: #333;
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.instructions-box ol, .instructions-box ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.instructions-box li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.instructions-box code {
|
||||
background: white;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #d63384;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.hint {
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.hint strong {
|
||||
color: #007bff;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.hint pre {
|
||||
background: #f0f0f0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8em;
|
||||
color: #333;
|
||||
margin: 0
|
||||
246
javascript/JS_Esercizi 11 - API/index.html
Normal file
246
javascript/JS_Esercizi 11 - API/index.html
Normal file
@@ -0,0 +1,246 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hub Esercizi API</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.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>🔌 API REST</h1>
|
||||
<p class="subtitle">Esercizi Semplificati - Da Base a CRUD</p>
|
||||
|
||||
<!-- TUTORIAL -->
|
||||
<h2>Tutorial Introduttivo</h2>
|
||||
<div class="exercise-list">
|
||||
<a href="tutorial/index.html" class="card">
|
||||
<div class="icon"></div>
|
||||
<div class="info">
|
||||
<h3>Tutorial Interattivo</h3>
|
||||
<p>Impara API, Fetch e Async/Await con 10 step</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- ESERCIZI BASE -->
|
||||
<h2>Esercizi su Server Locale</h2>
|
||||
<div class="exercise-list">
|
||||
<a href="01_get_singolo_utente/index.html" class="card">
|
||||
<div class="icon">📊</div>
|
||||
<div class="info">
|
||||
<h3>GET Singolo Utente <span class="difficulty easy">Facile</span></h3>
|
||||
<p>Recupera e visualizza un utente dal server</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="02_get_lista_utenti/index.html" class="card">
|
||||
<div class="icon">👥</div>
|
||||
<div class="info">
|
||||
<h3>GET Lista Utenti <span class="difficulty easy">Facile</span></h3>
|
||||
<p>Crea card dinamiche da un array di utenti</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="03_utente_e_post/index.html" class="card">
|
||||
<div class="icon">📝</div>
|
||||
<div class="info">
|
||||
<h3>Utente + Post <span class="difficulty medium">Medio</span></h3>
|
||||
<p>Fetch multipli e relazioni tra dati</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="04_todo_app_crud/index.html" class="card">
|
||||
<div class="icon">✅</div>
|
||||
<div class="info">
|
||||
<h3>Todo App CRUD <span class="difficulty hard">Difficile</span></h3>
|
||||
<p>GET, POST, PUT, DELETE - App completa</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2>Esercizi su Server Esterno</h2>
|
||||
<div class="exercise-list">
|
||||
<a href="05_meteo/index.html" class="card">
|
||||
<div class="icon">🌤️</div>
|
||||
<div class="info">
|
||||
<h3>App Meteo <span class="difficulty medium">Medio</span></h3>
|
||||
<p>API pubblica Open-Meteo - No server locale</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="06_pokedex/index.html" class="card">
|
||||
<div class="icon">🔴</div>
|
||||
<div class="info">
|
||||
<h3>Pokédex <span class="difficulty medium">Medio</span></h3>
|
||||
<p>API pubblica PokéAPI - No server locale</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="important-note">
|
||||
<strong>⚠️ Prima di Iniziare:</strong><br>
|
||||
Per esercizi 1-4: avvia il server con <code>cd server-api</code> e poi <code>npm start</code>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
93
javascript/JS_Esercizi 11 - API/tutorial/index.html
Normal file
93
javascript/JS_Esercizi 11 - API/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">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<title>API e Asincronia</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="controls">
|
||||
<h1>Tutorial: API e Asincronia</h1>
|
||||
<button id="btn-esegui">▶️ Esegui Codice</button>
|
||||
<button id="btn-reset">🧹 Pulisci Console</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. Il concetto di Attesa (Simulazione)",
|
||||
description: "Le API sono lente. Simuliamo un ritardo di 2 secondi prima di mostrare il messaggio.",
|
||||
outputLabel: "Output Step 1:"
|
||||
},
|
||||
{
|
||||
title: "2. Prima chiamata Fetch (GET)",
|
||||
description: "Recuperiamo un oggetto JSON reale da un server pubblico.",
|
||||
outputLabel: "Dati ricevuti:"
|
||||
},
|
||||
{
|
||||
title: "3. Estrazione Dati (JSON)",
|
||||
description: "La risposta grezza non basta. Dobbiamo convertirla ed estrarre il titolo.",
|
||||
outputLabel: "Titolo del Post:"
|
||||
},
|
||||
{
|
||||
title: "4. Gestione Errori (Try/Catch)",
|
||||
description: "Proviamo a chiamare un sito che non esiste per vedere se il codice sopravvive.",
|
||||
outputLabel: "Stato operazione:"
|
||||
},
|
||||
{
|
||||
title: "5. Lavorare con le Liste (Array)",
|
||||
description: "Scarichiamo 10 utenti e mostriamo solo i loro nomi.",
|
||||
outputLabel: "Elenco Utenti:"
|
||||
},
|
||||
{
|
||||
title: "6. Invio Dati (POST)",
|
||||
description: "Simuliamo l'invio di un nuovo post al server.",
|
||||
outputLabel: "Risposta Server:"
|
||||
}
|
||||
].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="script.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>
|
||||
115
javascript/JS_Esercizi 11 - API/tutorial/script.js
Normal file
115
javascript/JS_Esercizi 11 - API/tutorial/script.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// TUTORIAL INTERATTIVO: API, Fetch e Async/Await
|
||||
|
||||
// NON TOCCARE
|
||||
// Funzioni di supporto per il tutorial
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); // Simula server lento
|
||||
|
||||
// Funzione pricipale del tutorial
|
||||
async function eseguiTutorial() {
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 1. Il Concetto di Attesa ===
|
||||
* JavaScript di solito corre veloce. Con i server, deve aspettare.
|
||||
* La keyword 'await' serve a dire "Fermati qui finché l'operazione non finisce".
|
||||
*/
|
||||
// Questa funzione simula un server che ci mette 2 secondi a rispondere
|
||||
// TODO: Aggiungi 'await' prima di wait(2000) per far funzionare l'attesa.
|
||||
wait(2000);
|
||||
|
||||
mostraOutput(1, "Operazione completata!");
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 2. Fetch Base (GET) ===
|
||||
* Usiamo `fetch(url)` per chiamare un server vero.
|
||||
*/
|
||||
// TODO: crea una costante 'urlBase' con l'indirizzo dettato a lezione
|
||||
// TODO: Esegui una fetch a quell'URL.
|
||||
// Ricorda di mettere 'await' davanti a fetch!
|
||||
const urlBase = "https://sito-finto.com";
|
||||
const rispostaGrezza = null;
|
||||
|
||||
mostraOutput(2, rispostaGrezza ? "Risposta ricevuta (Response Object)" : "");
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 3. Estrarre il JSON ===
|
||||
* La risposta grezza contiene status code, headers, ecc.
|
||||
* A noi servono i dati. Dobbiamo usare il metodo .json().
|
||||
* ANCHE .json() è asincrono e vuole 'await'.
|
||||
*/
|
||||
let nomeUtente = "";
|
||||
|
||||
if (rispostaGrezza) {
|
||||
// TODO: Estrai i dati usando: await rispostaGrezza.json()
|
||||
const dati = {};
|
||||
|
||||
// TODO: Assegna a nomeUtente il valore di dati.nome
|
||||
nomeUtente = "NOME MANCANTE";
|
||||
}
|
||||
|
||||
mostraOutput(3, nomeUtente);
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 4. Gestione Errori (Try / Catch) ===
|
||||
* Se il server è giù o l'URL è sbagliato, fetch esplode.
|
||||
* Usiamo try/catch per gestire il problema.
|
||||
*/
|
||||
try {
|
||||
// TODO: Prova a fare una fetch a un URL sbagliato (es. 'https://sito-finto.com')
|
||||
// Ricorda l'await!
|
||||
|
||||
mostraOutput(4);
|
||||
} catch (errore) {
|
||||
mostraOutput(4, errore.message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 5. Liste di Dati (Array) ===
|
||||
* Spesso riceviamo un array di oggetti. Dobbiamo ciclarlo.
|
||||
*/
|
||||
// Scarichiamo 5 utenti
|
||||
const responseUtenti = await fetch(`${urlBase}/users?_limit=5`);
|
||||
const listaUtenti = await responseUtenti.json();
|
||||
|
||||
// TODO: Usa .map() o un ciclo for per creare un array contenente SOLO i nomi degli utenti.
|
||||
// Esempio: const nomi = listaUtenti.map(u => u.name);
|
||||
const soloNomi = [];
|
||||
|
||||
mostraOutput(5, soloNomi);
|
||||
|
||||
|
||||
/**
|
||||
* ===========================================
|
||||
* === 6. Inviare Dati (POST) ===
|
||||
* Per inviare dati, fetch accetta un secondo parametro di opzioni.
|
||||
*/
|
||||
// TODO: Completa l'oggetto con i dati mancanti
|
||||
const nuovoUtente = {
|
||||
nome: "",
|
||||
cognome: "",
|
||||
dataNascita: "", // Formato: 'YYYY-MM-DD'
|
||||
comune: "",
|
||||
email: "",
|
||||
attivo: true,
|
||||
avatar: "https://ui-avatars.com/api/?name=Nome+Cognome"
|
||||
};
|
||||
|
||||
// TODO: Completa la fetch.
|
||||
// Aggiungi method: 'POST'
|
||||
// Aggiungi headers: { 'Content-Type': 'application/json' }
|
||||
// Aggiungi body: JSON.stringify(nuovoUtente)
|
||||
const rispInvio = await fetch(`${urlBase}/posts`, {
|
||||
|
||||
});
|
||||
|
||||
const risultatoInvio = await rispInvio.json();
|
||||
mostraOutput(6, risultatoInvio);
|
||||
}
|
||||
116
javascript/JS_Esercizi 11 - API/tutorial/styles.css
Normal file
116
javascript/JS_Esercizi 11 - API/tutorial/styles.css
Normal file
@@ -0,0 +1,116 @@
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f0f2f5;
|
||||
color: #333;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
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;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-left: 6px solid #1a73e8;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -482,5 +482,55 @@
|
||||
"likes": 0,
|
||||
"data": "2023-10-14"
|
||||
}
|
||||
],
|
||||
"todos": [
|
||||
{
|
||||
"id": 1,
|
||||
"userId": 1,
|
||||
"titolo": "Imparare React",
|
||||
"completato": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"userId": 1,
|
||||
"titolo": "Creare un portfolio",
|
||||
"completato": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"userId": 2,
|
||||
"titolo": "Studiare CSS Grid",
|
||||
"completato": false
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"userId": 2,
|
||||
"titolo": "Aggiornare LinkedIn",
|
||||
"completato": true
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"userId": 2,
|
||||
"titolo": "Scrivere un blog post",
|
||||
"completato": false
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"userId": 2,
|
||||
"titolo": "Partecipare a un hackathon",
|
||||
"completato": false
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"userId": 1,
|
||||
"titolo": "Imparare TypeScript",
|
||||
"completato": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"userId": 1,
|
||||
"titolo": "Rifare il sito personale",
|
||||
"completato": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user