Practice and reinforce the concepts from Lesson 6
In this activity, you'll:
Total time: 60-75 minutes
Access the template here: Comparison Dashboard Template
git clone
and navigate to the template folderindex.html
in your browser to start!What you'll do: Pick your favorite comparison type Time: 5 minutes
Choose one of these exciting comparison projects:
Compare different Pokemon and their battle statistics
Compare characters from the Star Wars universe
Compare weather across multiple cities
Compare countries and their interesting facts
What you'll do: Create a function to fetch multiple items at once Time: 15 minutes
async function getMultiplePokemon(pokemonNames) {
const BASE_URL = 'https://pokeapi.co/api/v2/pokemon';
try {
console.log('🔥 Fetching Pokemon data...');
// TODO: Create array of fetch promises for all Pokemon
const pokemonPromises = pokemonNames.map(name =>
fetch(`${BASE_URL}/${name.toLowerCase()}`)
.then(response => {
if (!response.ok) throw new Error(`Pokemon ${name} not found`);
return response.json();
})
);
// TODO: Wait for all promises to complete
const pokemonData = await Promise.all(pokemonPromises);
console.log('✅ All Pokemon data received!');
return pokemonData;
} catch (error) {
console.error('❌ Failed to fetch Pokemon:', error);
throw error;
}
}
async function getMultipleCharacters(characterIds) {
const BASE_URL = 'https://swapi.dev/api/people';
try {
console.log('🌟 Fetching Star Wars characters...');
const characterPromises = characterIds.map(id =>
fetch(`${BASE_URL}/${id}/`)
.then(response => {
if (!response.ok) throw new Error(`Character ${id} not found`);
return response.json();
})
);
const charactersData = await Promise.all(characterPromises);
console.log('✅ All character data received!');
return charactersData;
} catch (error) {
console.error('❌ Failed to fetch characters:', error);
throw error;
}
}
async function getMultipleCitiesWeather(cities) {
const API_KEY = 'your-api-key-here'; // Get from OpenWeatherMap
try {
console.log('🌍 Fetching weather for all cities...');
const weatherPromises = cities.map(city =>
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric`)
.then(response => {
if (!response.ok) throw new Error(`Weather for ${city} not found`);
return response.json();
})
);
const weatherData = await Promise.all(weatherPromises);
console.log('✅ All weather data received!');
return weatherData;
} catch (error) {
console.error('❌ Failed to fetch weather:', error);
throw error;
}
}
Your task:
What you'll do: Create cards to show each item's data Time: 20 minutes
function displayPokemonCard(pokemon) {
const stats = pokemon.stats;
const types = pokemon.types.map(t => t.type.name).join(', ');
return `
<div class="item-card pokemon-card">
<div class="item-header">
<h3>${pokemon.name.charAt(0).toUpperCase() + pokemon.name.slice(1)}</h3>
<img src="${pokemon.sprites.front_default}" alt="${pokemon.name}" class="pokemon-sprite">
<button class="remove-btn" onclick="removeItem('${pokemon.name}')">×</button>
</div>
<div class="item-stats">
<div class="stat-group">
<div class="stat-label">Types:</div>
<div class="stat-value">${types}</div>
</div>
<div class="stat-item">⚔️ Attack: ${stats.find(s => s.stat.name === 'attack').base_stat}</div>
<div class="stat-item">🛡️ Defense: ${stats.find(s => s.stat.name === 'defense').base_stat}</div>
<div class="stat-item">❤️ HP: ${stats.find(s => s.stat.name === 'hp').base_stat}</div>
<div class="stat-item">💨 Speed: ${stats.find(s => s.stat.name === 'speed').base_stat}</div>
</div>
</div>
`;
}
function displayCharacterCard(character) {
return `
<div class="item-card character-card">
<div class="item-header">
<h3>${character.name}</h3>
<button class="remove-btn" onclick="removeItem('${character.name}')">×</button>
</div>
<div class="item-stats">
<div class="stat-item">📏 Height: ${character.height} cm</div>
<div class="stat-item">⚖️ Mass: ${character.mass} kg</div>
<div class="stat-item">🎂 Birth Year: ${character.birth_year}</div>
<div class="stat-item">👁️ Eye Color: ${character.eye_color}</div>
<div class="stat-item">💇 Hair Color: ${character.hair_color}</div>
</div>
</div>
`;
}
function displayCityWeather(cityData) {
return `
<div class="item-card weather-card">
<div class="item-header">
<h3>${cityData.name}, ${cityData.sys.country}</h3>
<button class="remove-btn" onclick="removeItem('${cityData.name}')">×</button>
</div>
<div class="temperature">
${Math.round(cityData.main.temp)}°C
</div>
<div class="item-stats">
<div class="stat-item">🌤️ ${cityData.weather[0].description}</div>
<div class="stat-item">🌡️ Feels like ${Math.round(cityData.main.feels_like)}°C</div>
<div class="stat-item">💧 Humidity: ${cityData.main.humidity}%</div>
<div class="stat-item">💨 Wind: ${cityData.wind.speed} m/s</div>
</div>
</div>
`;
}
Your task:
renderAllItems()
function to show all cardsWhat you'll do: Allow users to add items to the comparison Time: 15 minutes
let currentItems = []; // Your initial items here
let currentItemData = [];
async function addNewItem() {
const itemInput = document.getElementById('itemInput');
const newItem = itemInput.value.trim();
if (!newItem) {
alert('Please enter an item name');
return;
}
// Check if item already exists
if (currentItems.includes(newItem.toLowerCase())) {
alert('Item already added!');
return;
}
try {
// Fetch new item data (adjust based on your chosen API)
let newItemData;
if (comparisonType === 'pokemon') {
newItemData = await fetch(`https://pokeapi.co/api/v2/pokemon/${newItem.toLowerCase()}`)
.then(r => r.ok ? r.json() : Promise.reject('Not found'));
} else if (comparisonType === 'starwars') {
// For Star Wars, you might search by name or use character ID
newItemData = await searchStarWarsCharacter(newItem);
} else if (comparisonType === 'weather') {
newItemData = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${newItem}&appid=${API_KEY}&units=metric`)
.then(r => r.ok ? r.json() : Promise.reject('City not found'));
}
// Add to arrays
currentItems.push(newItem.toLowerCase());
currentItemData.push(newItemData);
// Re-render the display
renderAllItems(currentItemData);
itemInput.value = ''; // Clear input
} catch (error) {
alert(`Could not find ${newItem}`);
}
}
function removeItem(itemName) {
const index = currentItems.findIndex(item =>
item.toLowerCase() === itemName.toLowerCase()
);
if (index > -1) {
currentItems.splice(index, 1);
currentItemData.splice(index, 1);
renderAllItems(currentItemData);
}
}
Your task:
What you'll do: Show interesting comparisons between items Time: 15 minutes
function updatePokemonStats() {
if (currentItemData.length < 2) {
document.getElementById('comparisonStats').style.display = 'none';
return;
}
// Find strongest in each category
const attacks = currentItemData.map(p => p.stats.find(s => s.stat.name === 'attack').base_stat);
const defenses = currentItemData.map(p => p.stats.find(s => s.stat.name === 'defense').base_stat);
const speeds = currentItemData.map(p => p.stats.find(s => s.stat.name === 'speed').base_stat);
const strongestAttacker = currentItemData[attacks.indexOf(Math.max(...attacks))];
const strongestDefender = currentItemData[defenses.indexOf(Math.max(...defenses))];
const fastest = currentItemData[speeds.indexOf(Math.max(...speeds))];
document.getElementById('statsContent').innerHTML = `
<div class="stat-item">⚔️ Strongest Attacker: ${strongestAttacker.name} (${Math.max(...attacks)})</div>
<div class="stat-item">🛡️ Best Defender: ${strongestDefender.name} (${Math.max(...defenses)})</div>
<div class="stat-item">💨 Fastest: ${fastest.name} (${Math.max(...speeds)})</div>
<div class="stat-item">🏆 Team Size: ${currentItemData.length} Pokemon</div>
`;
document.getElementById('comparisonStats').style.display = 'block';
}
function updateCharacterStats() {
if (currentItemData.length < 2) return;
const heights = currentItemData.map(c => parseInt(c.height) || 0);
const masses = currentItemData.map(c => parseInt(c.mass) || 0);
const tallest = currentItemData[heights.indexOf(Math.max(...heights))];
const heaviest = currentItemData[masses.indexOf(Math.max(...masses))];
document.getElementById('statsContent').innerHTML = `
<div class="stat-item">📏 Tallest: ${tallest.name} (${tallest.height} cm)</div>
<div class="stat-item">⚖️ Heaviest: ${heaviest.name} (${heaviest.mass} kg)</div>
<div class="stat-item">👥 Characters: ${currentItemData.length}</div>
`;
document.getElementById('comparisonStats').style.display = 'block';
}
Your task:
Try these scenarios:
Easy: Add a "Refresh All" button to update all data
Medium: Add sorting options (by stats, alphabetical, etc.)
Hard: Create a "Battle Mode" for Pokemon or "Face-off Mode" for characters
When complete, you should have:
Great work on building a dynamic comparison dashboard! You've learned how to make parallel API requests and manage dynamic data. In the next lesson, we'll explore data visualization with charts to make your comparisons even more engaging.