Project 2: Weather Dashboard
In this project, we will build a Weather Dashboard that communicates with a public API. We will fetch weather statistics for any city input, parse the JSON response, handle network/HTTP errors, and update the webpage structure dynamically.
1. Project Features
- City name search input.
- Fetch temperature, weather descriptions, and icons.
- Informative error banners for missing or misspelled cities.
- Async loader states during API requests.
2. The HTML Structure
<!-- index.html -->
<div class="weather-card">
<h2>Weather Tracker</h2>
<div class="search-box">
<input type="text" id="city-input" placeholder="Enter city name..." value="London">
<button id="search-btn">Search</button>
</div>
<!-- Loading state container -->
<div id="loader" class="hidden">Loading...</div>
<!-- Result Container -->
<div id="weather-info" class="hidden">
<h3 id="city-name">London, GB</h3>
<img id="weather-icon" src="" alt="Weather Icon">
<h1 id="temp">15°C</h1>
<p id="desc">Cloudy</p>
</div>
<!-- Error Box -->
<div id="error-box" class="hidden">City not found. Please try again.</div>
</div>3. The JavaScript Implementation
For this project, we can query the free OpenWeatherMap API (or a mock service to test).
// app.js
const API_KEY = "YOUR_API_KEY_HERE"; // Substitute with your real OpenWeatherMap key
const BASE_URL = "https://api.openweathermap.org/data/2.5/weather";
const cityInput = document.querySelector('#city-input');
const searchBtn = document.querySelector('#search-btn');
const loader = document.querySelector('#loader');
const infoBox = document.querySelector('#weather-info');
const errorBox = document.querySelector('#error-box');
// Target Nodes
const cityNameNode = document.querySelector('#city-name');
const iconNode = document.querySelector('#weather-icon');
const tempNode = document.querySelector('#temp');
const descNode = document.querySelector('#desc');
// 1. Core Fetch Logic
async function fetchWeather(city) {
// Show loader, hide result and errors
loader.classList.remove('hidden');
infoBox.classList.add('hidden');
errorBox.classList.add('hidden');
try {
const url = `${BASE_URL}?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;
const response = await fetch(url);
// Check if query is valid (e.g. 200 OK)
if (!response.ok) {
throw new Error("City not found");
}
const data = await response.json();
displayWeather(data);
} catch (error) {
showError(error.message);
} finally {
loader.classList.add('hidden');
}
}
// 2. Rendering Data to DOM
function displayWeather(data) {
cityNameNode.textContent = `${data.name}, ${data.sys.country}`;
tempNode.textContent = `${Math.round(data.main.temp)}°C`;
descNode.textContent = data.weather[0].description;
// Load official OpenWeatherMap icon
const iconCode = data.weather[0].icon;
iconNode.src = `https://openweathermap.org/img/wn/${iconCode}@2x.png`;
infoBox.classList.remove('hidden');
}
// 3. Handling Errors
function showError(message) {
errorBox.textContent = message;
errorBox.classList.remove('hidden');
}
// 4. Bind Search Click event
searchBtn.addEventListener('click', () => {
const city = cityInput.value.trim();
if (city) {
fetchWeather(city);
}
});
// 5. Trigger query when hitting "Enter" inside input box
cityInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
searchBtn.click();
}
});4. Key Takeaways
encodeURIComponent(): Always encode query parameters to prevent syntax errors caused by space characters in names (e.g., "New York").- Robust Loader UX: Informing users that an action is running is essential for web accessibility and user trust.
- Graceful Degradation: Always handle errors inside
catchto prevent scripts from breaking when APIs go down.
In the next project, we will build a fully functioning Calculator focusing on Event Delegation.
Published on Last updated: