Skip to content

Weather App. Team: Darius, Therese, Tavan, Kasia, Jasmin #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
83f85cd
html structure
Mar 18, 2025
cbe403f
ignore typechecking
Mar 18, 2025
a6ccbb1
fetch + function
Mar 18, 2025
a30f6d4
adjusted fetch function weather + localstorage
Mar 19, 2025
fe6ffea
updated localstorage
Mar 19, 2025
c7ee945
added styling to HTML
Mar 19, 2025
de82ce0
added searchabr, display weather, storage
Mar 19, 2025
5503506
adding missing html and styling
Mar 19, 2025
f76de5d
added images to assets
Mar 19, 2025
9d675a4
Merge branch 'main' of https://github.com/JasminHed/newjs-project-wea…
Mar 19, 2025
8352def
added html and css
Mar 19, 2025
7440a9c
removed extra side button
Mar 19, 2025
587c168
updated error bracket
Mar 19, 2025
1060ae5
commented out forecast function
Mar 19, 2025
baf6a38
removed comments and old code
Mar 19, 2025
ab46f76
updated js
Mar 20, 2025
0153fbe
new html
Mar 20, 2025
d2cde95
new css
Mar 20, 2025
3b593fe
tweaked js
Mar 20, 2025
e8e88c8
removed the new script file
Mar 20, 2025
dac19ae
created TS
Mar 20, 2025
c5b04e4
updated ts file
Mar 20, 2025
5a1e3a7
compiled ts+js and added slidebutton
Mar 21, 2025
fde6dc8
added eventlistener to enter key
Mar 22, 2025
16b9e8c
removed old ts code
Mar 22, 2025
b13c6c4
updated all functions to same
Mar 24, 2025
435a337
added functions for getting api icons in weather + forecast, changed css
Mar 24, 2025
b465cc5
removed old code and comments
Mar 24, 2025
dc21e55
Update README.md
JasminHed Apr 8, 2025
0f95aef
screen reader labeling + focus for tabbing
Apr 9, 2025
b6b659f
Merge branch 'main' of https://github.com/JasminHed/newjs-project-wea…
Apr 9, 2025
9e4e688
removed redundant focus input+button
Apr 9, 2025
0c5e797
Update README.md
JasminHed Apr 26, 2025
9de7171
updated readme
JasminHed Jun 30, 2025
3d40b68
resolved conflict
JasminHed Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,37 @@
# js-project-weather-app
# js-project-weather-app

Live Demo: nordicweatherapp.netlify.app

Overview
A modern weather application that provides current conditions and 4-day forecasts for cities worldwide. This project challenged me to work with real-time weather data from external APIs and create an intuitive interface for checking weather conditions. Building this app taught me how to handle dynamic data, manage API calls efficiently, and present complex information in a user-friendly way.

Tech Stack
Frontend: HTML5, CSS3, JavaScript (ES6+)
API: Weather API (OpenWeatherMap/WeatherAPI)
Layout: CSS Grid, Flexbox
Data Handling: Fetch API, JSON processing
Deployment: Netlify
Version Control: Git/GitHub

Features
Search for weather in selected cities
Current weather conditions with temperature and description
4-day extended weather forecast
Responsive layout optimized for all devices
Real-time data updates from weather API
Error handling for invalid city searches
Loading states during API requests

Setup Instructions
git clone [your-repo-url]
cd nordic-weather-app

Open in browser
open index.html
No installation needed - it's pure HTML, CSS, and JavaScript

What I Learned
Working with third-party weather APIs and API keys
Handling asynchronous JavaScript and fetch requests
Creating intuitive search functionality
Managing API rate limits and error responses
Binary file added assets/Brokenclouds.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Cloudy.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Fewclouds.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Group 13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Group 15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Group 16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Night.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Subtraction 2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Sunny.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Sunny.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/Sunrise.jpg.avif
Binary file not shown.
Binary file added assets/Sunset.jpg.avif
Binary file not shown.
Binary file added assets/searchglass.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/sun.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
190 changes: 190 additions & 0 deletions dist/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"use strict";
// @ts-nocheck
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
document.addEventListener('DOMContentLoaded', function () {
// API information
const API_KEY = '3bad52890d7306cc268371520cbaace6';
const BASE_URL = 'https://api.openweathermap.org/data/2.5/forecast';
// List of default cities
const cities = ['Stockholm', 'Gothenburg', 'Oslo'];
let weeklyForecast = {};
let currentCityIndex = 0;
function fetchWeather(city) {
return __awaiter(this, void 0, void 0, function* () {
try {
const response = yield fetch(`${BASE_URL}?q=${city}&units=metric&appid=${API_KEY}`);
const data = yield response.json();
if (data.cod !== "200") {
throw new Error(data.message || "Failed to fetch weather data.");
}
return data;
}
catch (error) {
console.error(`Error fetching weather data for ${city}:`, error);
return null;
}
});
}
function getDayName(dateString) {
const date = new Date(dateString);
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
return days[date.getDay()];
}
function fetchAndStoreWeather(city) {
return __awaiter(this, void 0, void 0, function* () {
const data = yield fetchWeather(city);
if (!data || !data.list) {
return;
}
const todayData = data.list.find(entry => entry.dt_txt.includes("12:00:00")) || data.list[0];
const todayDate = todayData.dt_txt.split(" ")[0];
const sunriseTime = new Date(data.city.sunrise * 1000).toLocaleTimeString("sv-SE", { hour: "2-digit", minute: "2-digit" });
const sunsetTime = new Date(data.city.sunset * 1000).toLocaleTimeString("sv-SE", { hour: "2-digit", minute: "2-digit" });
const todayForecast = {
city: data.city.name,
day: getDayName(todayDate),
weather: todayData.weather[0].description,
icon: todayData.weather[0].icon,
temp: todayData.main.temp,
wind: todayData.wind.speed,
sunrise: sunriseTime,
sunset: sunsetTime,
};
const dailyForecasts = {};
data.list.forEach(entry => {
const date = entry.dt_txt.split(' ')[0];
const hour = entry.dt_txt.split(' ')[1].split(':')[0];
if (hour === '12' && date !== todayDate) {
dailyForecasts[date] = {
date: date,
day: getDayName(date),
icon: entry.weather[0].icon,
weather: entry.weather[0].description,
temp: entry.main.temp,
wind: entry.wind.speed,
};
}
});
const upcomingForecast = Object.values(dailyForecasts).slice(0, 4);
weeklyForecast[city] = {
today: todayForecast,
upcoming: upcomingForecast
};
localStorage.setItem("weatherData", JSON.stringify(weeklyForecast));
displayTodaysWeather(todayForecast);
displayWeeklyWeather(upcomingForecast);
updateBackground(todayForecast.weather, todayForecast.icon);
});
}
function displayTodaysWeather(forecast) {
const weatherContent = document.getElementById('weather-content');
if (!weatherContent)
return;
weatherContent.innerHTML = `
<div class="weather-icon">
<img id="main-icon" src="https://openweathermap.org/img/wn/${forecast.icon}@2x.png" alt="${forecast.weather}">
</div>
<p id="temperature">${Math.round(forecast.temp)}°C</p>
<p id="city">${forecast.city}</p>
<p id="weather">${forecast.weather}</p>
<div class="sunrise-sunset">
<p id="sunrise">Sunrise: ${forecast.sunrise}</p>
<p id="sunset">Sunset: ${forecast.sunset}</p>
</div>
`;
}
function displayWeeklyWeather(forecastList) {
const forecastTable = document.querySelector("#weather-forecast table");
if (!forecastTable)
return;
const rows = forecastTable.getElementsByTagName("tr");
if (!rows || rows.length === 0)
return;
forecastList.forEach((forecast, index) => {
if (index < rows.length) {
const dayCell = rows[index].querySelector(`#day${index + 1}`);
if (dayCell)
dayCell.textContent = forecast.day;
const iconCell = rows[index].querySelector(`#iconday${index + 1}`);
if (iconCell) {
iconCell.innerHTML = `<img src="https://openweathermap.org/img/wn/${forecast.icon}.png" alt="${forecast.weather}">`;
}
const tempCell = rows[index].querySelector(`#tempday${index + 1}`);
if (tempCell)
tempCell.textContent = `${Math.round(forecast.temp)}°C`;
const windCell = rows[index].querySelector(`#windday${index + 1}`);
if (windCell)
windCell.textContent = `${forecast.wind} m/s`;
}
});
}
function updateBackground(weatherDescription, iconCode) {
const container = document.querySelector('.container');
if (!container)
return;
container.classList.remove('rainy', 'cloudy', 'clear', 'snowy', 'daytime', 'nighttime');
const isDaytime = iconCode.endsWith('d');
container.classList.add(isDaytime ? 'daytime' : 'nighttime');
if (weatherDescription.includes('rain') || weatherDescription.includes('drizzle')) {
container.classList.add('rainy');
}
else if (weatherDescription.includes('cloud')) {
container.classList.add('cloudy');
}
else if (weatherDescription.includes('clear')) {
container.classList.add('clear');
}
else if (weatherDescription.includes('snow')) {
container.classList.add('snowy');
}
}
function cycleCity() {
currentCityIndex = (currentCityIndex + 1) % cities.length;
const city = cities[currentCityIndex];
fetchAndStoreWeather(city);
}
function initializeEventListeners() {
const searchButton = document.getElementById("search-button");
const inputField = document.getElementById("input-field");
const nextSideButton = document.getElementById('next-side-button');
if (searchButton) {
searchButton.addEventListener("click", function () {
if (inputField && inputField.value.trim()) {
fetchAndStoreWeather(inputField.value.trim());
}
});
}
if (inputField) {
inputField.addEventListener("keydown", function (event) {
if (event.key === "Enter" && inputField.value.trim()) {
fetchAndStoreWeather(inputField.value.trim());
}
});
}
if (nextSideButton) {
nextSideButton.addEventListener('click', cycleCity);
}
else {
console.error("Could not find button with ID 'next-side-button'");
}
}
initializeEventListeners();
fetchAndStoreWeather("Stockholm");
const savedData = localStorage.getItem("weatherData");
if (savedData) {
try {
weeklyForecast = JSON.parse(savedData);
}
catch (e) {
console.error("Failed to parse saved weather data:", e);
}
}
});
106 changes: 106 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=, initial-scale=1.0"
>
<link
rel="stylesheet"
href="style.css"
>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
>
<title>Weather App</title>
</head>

<body>

<div class="container">
<h1 class="sr-only">Weather App</h1>

<div class="overlay"></div>
<div class="input-container">
<label
for="input-field"
class="sr-only"
>Search city</label>
<input
type="text"
id="input-field"
placeholder="Search city"
>
<button
id="search-button"
aria-label="Search"
>
<i
class="fa-solid fa-magnifying-glass"
aria-hidden="true"
></i>
</button>
</div>

<div>
<div
class="weather-content"
id="weather-content"
>
</div>


<button
id="next-side-button"
aria-label="Show next City"
><i
class="fa-solid fa-angle-right"
aria-hidden="true"
></i></button>

<div
class="weather-forecast"
id="weather-forecast"
>
<h2 class="sr-only">Weather Forecast</h2>

<table>
<caption class="sr-only">4-day weather forecast</caption>
<tr>
<td id="day1"></td>
<td id="iconday1"></td>
<td id="tempday1"></td>
<td id="windday1"></td>
</tr>
<tr>
<td id="day2"></td>
<td id="iconday2"></td>
<td id="tempday2"></td>
<td id="windday2"></td>
</tr>
<tr>
<td id="day3"></td>
<td id="iconday3"></td>
<td id="tempday3"></td>
<td id="windday3"></td>
</tr>
<tr>
<td id="day4"></td>
<td id="iconday4"></td>
<td id="tempday4"></td>
<td id="windday4"></td>
</tr>
</table>
</div>
</div>



<script src="script.js"></script>

</body>

</html>
Loading