Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Weather Project
## Weather Project using Open Weather Map's API

This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.

If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io.

![Image](https://github.com/user-attachments/assets/1dc34666-82f2-4980-ba3c-720f3240d04d)
58 changes: 58 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH"
crossorigin="anonymous"
/>
<link rel="stylesheet" href="style.css" />
<title>Weather App Eval</title>
</head>
<body>
<div class="container-fluid justify-content-center align-items-center">
<div class="row">
<div class="d-grid col-xxl-6 mx-auto">
<div class="page-header text-center mt-5">
<h1>Weather App Project</h1>
<hr />
</div>

<!-- form input field and search button -->
<div id="form-group">
<form class="search-form">
<div class="input-group mb-4 mt-4">
<input
type="text"
class="form-control"
id="city-input"
placeholder="Enter City Here"
/>
<button type="submit" class="btn btn-primary search">
Get Weather
</button>
</div>
</form>
</div>

<hr />

<!-- row for current weather -->
<div
class="current-weather row d-flex align-items-center justify-content-center"
></div>

<!-- 5 day forecast container and row -->
<div class="container-fluid">
<div class="forecast row"></div>
</div>
</div>
</div>
</div>

<script src="main.js"></script>
</body>
</html>
170 changes: 170 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
const cityInput = document.querySelector('#city-input');
const currentWeatherContainer = document.querySelector('.current-weather');
const forecastContainer = document.querySelector('.forecast');

// click event listener for city input and the search button
document.querySelector('.search').addEventListener('click', function(event) {
event.preventDefault();

const city = cityInput.value;
if (city !== '') {
getWeather(city);

cityInput.value = '';

} else {
alert(('Please enter a city.'));
};
});

/**
* Fetches data from the specified URL using the GET method and returns the response as JSON.
* If the response is not OK, throws an error with a message from the response or a default message.
* If an error occurs during the fetch, displays an error message and returns an empty object.
*
* @param {string} url - The URL to fetch data from.
* @return {Promise<Object>} A promise that resolves to the JSON response data.
* @throws {Error} If the response is not OK.
*/
async function fetchApiData(url){
try {
const response = await fetch(url, { method: 'GET', dataType: 'json' });
const data = await response.json();

if (response.ok) {
return data;
} else {
throw new Error(data.message || 'Error fetching data');
}
}
catch(error) {
alert('Not a valid city. Please try again.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, this is the only error you may have? I think there can be more

console.error('Error fetching weather data:', error);
return {};
}
};

/**
* Fetches weather data for a given city using the OpenWeatherMap API.
*
* @param {string} city - The name of the city to fetch weather data for.
* @return {Promise<void>} A promise that resolves when the weather data has been fetched and displayed.
* Rejects with an error if there was an issue fetching the data.
*/
async function getWeather(city) {
const apiKey = '3a16b1625fdf1f83be7f01c3a15bd5f0';
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=imperial`;

try {
const weatherData = await fetchApiData(apiUrl);

// Get coordinates for the forecast API
const { lon, lat } = weatherData.coord;
const forecastUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${apiKey}&units=imperial`;
const forecastData = await fetchApiData(forecastUrl);

displayCurrentWeather(weatherData);
displayForecast(processForecastData(forecastData.list));
}
catch(error) {
console.error('Error fetching weather data:', error);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also display the error to the user.

}
};

/**
* Displays the current weather information on the page.
*
* @param {Object} data - The weather data object containing the current weather information.
* @return {void} This function does not return anything.
*/
function displayCurrentWeather(data) {
currentWeatherContainer.replaceChildren();
forecastContainer.replaceChildren();

// Destructure the necessary data
const { name: city, main: {temp}, weather: [{description, icon}] } = data;

// Create a template string for the current weather
const currentWeatherTemplate = `
<div class="col-3 mt-3">
<p class="fs-3 fw-bold">${city}</p>
<p class="fs-3 fw-bold">${Math.round(temp)}&deg;F</p>
<p class="fs-4 fst-italic text-capitalize">${description}</p>
Comment on lines +90 to +92

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great use of concatenation

</div>
<div class="col-3">
<img
src="https://openweathermap.org/img/wn/${icon}@2x.png"
alt="weather icon"
/>
</div>
`
// Insert the template into the DOM
currentWeatherContainer.insertAdjacentHTML('beforeend', currentWeatherTemplate);
};

/**
* Processes the forecast data to calculate the average temperature and other information for each day.
*
* @param {Array} data - An array of objects containing the forecast data.
* @return {Array} An array of objects containing the processed forecast data for the next 5 days.
*/
function processForecastData(data) {
const dailyData = {};

data.forEach(item => {
const date = new Date(item.dt * 1000);
const day = date.toLocaleDateString('en-US', { weekday: 'long' });
const temp = item.main.temp;
const icon = item.weather[0].icon;
const description = item.weather[0].description;

if (!dailyData[day]) {
dailyData[day] = {
temps: [],
icons: [],
descriptions: []
};
}

dailyData[day].temps.push(temp);
dailyData[day].icons.push(icon);
dailyData[day].descriptions.push(description);
});

const result = [];
for (const day in dailyData) {
const temps = dailyData[day].temps;
const avgTemp = temps.reduce((sum, t) => sum + t, 0) / temps.length;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract to a new function for readability

const icon = dailyData[day].icons[0];
const description = dailyData[day].descriptions[0];

result.push({
day,
avgTemp: avgTemp.toFixed(1),
icon,
description
});
}

return result.slice(0, 5); // Return only the next 5 days
};


/**
* Displays the forecast information for the next 5 days.
* @param {Object} data - The forecast data containing the list of forecast items.
* @return {void} This function does not return anything.
*/
function displayForecast(data) {
data.forEach(dayData => {
const forecastTemplate = `
<div class="d-grid col mt-5 mb-5 bg-transparent border border-secondary shadow-sm">
<p class="fs-5 fw-bold">${dayData.day}</p>
<img src="https://openweathermap.org/img/wn/${dayData.icon}@2x.png" alt="weather icon" />
<p class="fs-5 fw-bold">${Math.round(dayData.avgTemp)}&deg;F</p>
<p class="fs-5 fst-italic text-capitalize">${dayData.description}</p>
</div>
`;
forecastContainer.insertAdjacentHTML('beforeend', forecastTemplate);
})
};
28 changes: 28 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
body {
width: 100%;
min-height: 100vh;
text-align: center;
background-color: #bfdfec;
}

@media (max-width: 576px) {

.page-header h1 {
font-size: 1.5rem;
}

.input-group {
flex-direction: column;
}

.input-group .form-control,
.input-group .btn {
width: 100%;
margin-bottom: 10px;
}

.forecast {
width: 100%;
margin-bottom: 10px;
}
}