Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b28d494
Initial Commit
bhdoggett Nov 28, 2024
0d86998
Work on html template in index.html
bhdoggett Nov 28, 2024
96419af
Continue formatting the forecast tempate in index.html
bhdoggett Nov 28, 2024
ea67da6
Current Weather data fetch function is working.
bhdoggett Nov 29, 2024
f5793ba
I can now create a url for the fiveDayData function with the longitud…
bhdoggett Nov 30, 2024
2bb1f62
I added https:// to the fiveDayURL and now I can retrieve that data.
bhdoggett Nov 30, 2024
5a6ca2e
moved api key to a seperate file that isn't tracked by git
bhdoggett Dec 1, 2024
9565456
Working on processing the 5 day forcast data. I can currently sepeare…
bhdoggett Dec 3, 2024
8594840
I can now return an object for each day with a summary of the average…
bhdoggett Dec 3, 2024
d25f785
I can now use the summary data to populate the html. But now I realiz…
bhdoggett Dec 3, 2024
91eaf79
Update main.js to enable posting of weekday names for the five day fo…
bhdoggett Dec 5, 2024
b144c92
Refactor to use async await in the getNow function rather than .then(…
bhdoggett Dec 5, 2024
6313d6c
Extract child functions from within getNow and getFive day and sepera…
bhdoggett Dec 6, 2024
563be18
Update Code to default the page to start with the weather forecast fo…
bhdoggett Dec 6, 2024
7b2e68f
Minor edit to Readme
bhdoggett Dec 6, 2024
82eef76
Remove countries+cities.json
bhdoggett Dec 6, 2024
6c1e954
Update .gitignore to ignore countries-cities. This is for a future po…
bhdoggett Dec 6, 2024
f3eb71c
Simplify index range code in processDayData function to remove the se…
bhdoggett Dec 6, 2024
9f0420d
Simplify five-day forecast template to iterate over a singleDay templ…
bhdoggett Dec 6, 2024
14588df
Remove console logs and comments
bhdoggett Dec 6, 2024
06958ea
Remove style.css. Unnecessary because bootrap is used for styling.
bhdoggett Dec 6, 2024
ea295f8
Delete unneccessary 'addFiveDay'in previous line 48
bhdoggett Dec 6, 2024
c72e3de
No longer need .gitignore file
bhdoggett Dec 6, 2024
f38cc6a
Fix five-day forecast section's day template to properly display imag…
bhdoggett Dec 8, 2024
253f3cd
Update processDayData function to separate multiple operations into d…
bhdoggett Dec 12, 2024
c9720d2
Update Readme and update main.js to standardize function syntax to ES…
bhdoggett Dec 12, 2024
b0e4d50
Fix bug in processisng avg temp
bhdoggett Dec 13, 2024
c5b97f4
Remove extra space in index.html and refactor processDayData to push …
bhdoggett Dec 13, 2024
e38200a
Update comments
bhdoggett Dec 13, 2024
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
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.

This is a simple weather application which will display the current weather and a five-day forecast for any geographic location based on an input of City, State (if applicable) and Country. The application will begin with the user's current location as a default.

Data is taken from Open Weather API: https://openweathermap.org/api.
55 changes: 55 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<title>Weather Project</title>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>

<!-- Bootstrap CSS -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="page-header my-3">
<h1>Weather Project</h1>
</div>
</div>
<div class="row">
<div class="col-md-6 offset-md-3">
<div class="input-group mb-3">
<input
type="text"
class="form-control"
id="query"
placeholder="City, State, Country"
/>
<div class="input-group-append">
<button class="btn btn-primary search" type="button">
Search
</button>
</div>
</div>

<hr />
</div>
</div>

<div class="row justify-content-center forecast-today mt-2 mb-4" id="now">
</div>

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

<script src="main.js" type="module"></script>
</body>
</html>
285 changes: 285 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
const apiKey = "6427275c4ee8b157888fdf144b2fc5ca";

Choose a reason for hiding this comment

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

usually you don't put keys in github you would read it from an ENV variable but I'm sure your instructor will go over this.

Copy link
Author

Choose a reason for hiding this comment

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

Since it's a free api subscription, they told me I could include. Haven't tackled node yet.

const units = "imperial";

// Fetch current cocation to display default weather forecast.This returns an object in the form of {lat:____, lon:____}
const getCurrentLocation = () => {
const options = {
enableHighAccuracy: true,
maximumAge: 0,
};

return new Promise((resolve, reject) => {

Choose a reason for hiding this comment

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

I like that you wrap this long running process in a promise.

navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
lon: position.coords.longitude,
lat: position.coords.latitude,
});
},
(error) => reject(console.warn(`ERROR(${error.code}): ${error.message}`)),
options
);
});
};

// Using the current location, fetch the appropriate weather data from OpenWeather API
const fetchCurrentLocationData = async (location) => {
const url = `https://api.openweathermap.org/data/2.5/weather?lat=${location.lat}&lon=${location.lon}&appid=${apiKey}&units=${units}`;

const fetchedData = await fetch(url, {
method: "GET",
data: "json",
});

const currentLocationData = await fetchedData.json();

return currentLocationData;
};

// Update HTML with current/default location weather data
const addCurrentLocation = async () => {
try {
const location = await getCurrentLocation();
const locationData = await fetchCurrentLocationData(location);
addNow(locationData);

const fiveDayData = await fetchFiveDayData(location);
const processedData = await processFiveDayData(fiveDayData);
addFiveDay(processedData);
} catch (error) {
console.error("Error processing data:", error);

Choose a reason for hiding this comment

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

this doesnt really help the user.

}
};

addCurrentLocation();

// Use fetchNow() and addNow() functions to update the current weather forecast section of the HTML from a given locaiton query
const getNow = async () => {
try {
const nowData = await fetchNow();
addNow(nowData);
} catch (error) {
console.error("Error processing data:", error);
}
};

// Fetch current weatehr data for the location queried
const fetchNow = async () => {
const query = document.querySelector("#query").value.replace(/\s+/g, "%20");

const url = `https://api.openweathermap.org/data/2.5/weather?q=${query}$&limit=1&appid=${apiKey}&units=${units}`;

const fetchedData = await fetch(url, {
method: "Get",
dataType: "json",
});

const nowData = await fetchedData.json();

return nowData;
};

// Use template to update HTML for the current weather forecast based on data from the queried location
const addNow = async (data) => {
const nowSection = document.querySelector("#now");
nowSection.replaceChildren();

const template = `
<div class="row justify-content-center forecast-today my-3" id="now">
<div class="col-md-6">
<h3 class="text-center">${data.main.temp.toFixed(1)}°</h3>
<h4 class="text-center">${data.name}</h4>
<h5 class="text-center">${data.weather[0].main}</h5>
</div>
<div class="col-md-6">
<img
class="img-fluid mx-auto d-block"
src="https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png" )
alt="unable to load image"
/>
</div>
</div>
`;

nowSection.insertAdjacentHTML("beforeend", template);
};

// Use a series of functions to populate the 5-day forecast section of the HTML
const getFiveDay = async () => {
try {
const coordinates = await fetchCoordinates();
const fiveDayData = await fetchFiveDayData(coordinates);
const processedData = await processFiveDayData(fiveDayData);
addFiveDay(processedData);
} catch (error) {
console.error("Error processing data:", error);
}
};

// Use Open Weather Geolocation API to return data, {lat:____, lon:___}, for the queried location
const fetchCoordinates = async () => {
const query = document.querySelector("#query").value.replace(/\s+/g, "%20");

const coordinatesCall = await fetch(
`https://api.openweathermap.org/geo/1.0/direct?q=${query}&limit=1&appid=${apiKey}
`,
{
method: "GET",
dataType: "json",
}
);

const coordinatesArray = await coordinatesCall.json();
const coordinates = coordinatesArray[0];

return coordinates;
};

// Given an object with {lat:___, lon:___} syntax, fetch five-day weather information. This returns 40 instances of data which must be divided into 5 sets of 8.
const fetchFiveDayData = async (coordinates) => {
const fiveDayURL = `https://api.openweathermap.org/data/2.5/forecast?lat=${coordinates.lat}&lon=${coordinates.lon}&appid=${apiKey}&units=${units}`;

const fetchFiveDay = await fetch(fiveDayURL, {

Choose a reason for hiding this comment

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

the object is static, can be a const on top of the file

method: "GET",
dataType: "json",
});

const fiveDayData = await fetchFiveDay.json();

return fiveDayData;
};

// Colate data from each day in the five-day forecast.
const processFiveDayData = async (data) => {

Choose a reason for hiding this comment

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

this function is still complex, nested and logic that helpers can help with readability and debugging

const dividedDayData = [];

const processDayData = (day) => {
const indexRange = [(day - 1) * 8, day * 8 - 1];

const getAvgTemp = () => {
let acc = 0;
let timeIndex = 0;

for (let i = indexRange[0]; i <= indexRange[1]; i++) {
const currentTemp = data.list[i].main.temp;
acc += (currentTemp - acc) / (timeIndex + 1);
timeIndex++;
}

return acc.toFixed(1);
};

const getSummaryIcon = () => {
let icons = {};

Choose a reason for hiding this comment

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

can be const.


for (let i = indexRange[0]; i <= indexRange[1]; i++) {
const icon = data.list[i].weather[0].icon;

if (icons.hasOwnProperty(icon)) {
icons[icon]++;
} else {
icons[icon] = 1;
}
}

let iconMaxCount = 0;
let iconSummary = "";

for (let key in icons) {
if (icons[key] > iconMaxCount) {
iconMaxCount = icons[key];
iconSummary = key;
}
}
return iconSummary;
};

const getDayName = () => {
const dt_txt = new Date(data.list[indexRange[0]].dt_txt);

const dateFormatter = new Intl.DateTimeFormat("en-US", {
weekday: "long",
});

return dateFormatter.format(dt_txt);
};

const getSummaryWeatherDescription = () => {
let dayData = {};
let weatherDescriptions = {};

for (let i = indexRange[0]; i <= indexRange[1]; i++) {
const weatherDescription = data.list[i].weather[0].main;

if (weatherDescriptions.hasOwnProperty(weatherDescription)) {
weatherDescriptions[weatherDescription]++;
} else {
weatherDescriptions[weatherDescription] = 1;
}
}

let weatherDescriptionsMaxCount = 0;
let summaryDescription = "";

for (let key in weatherDescriptions) {
if (weatherDescriptions[key] > weatherDescriptionsMaxCount) {
weatherDescriptionsMaxCount = weatherDescriptions[key];
summaryDescription = key;
}
}
return summaryDescription;
};

return {
avgTemp: getAvgTemp(),
iconSummary: getSummaryIcon(),
weatherSummary: getSummaryWeatherDescription(),
dayName: getDayName(),
};
};

// Iterate through each day to colate all summary data
for (let i = 1; i <= 5; i++) {
dividedDayData.push(processDayData(i));
}

return dividedDayData;
};

// Update HTML with data given in the form returned by the processFiveDayData function
const addFiveDay = (summaryData) => {
const fiveDaySection = document.querySelector(".forecast-5-day");
fiveDaySection.replaceChildren();

const dayTemplate = (summaryItem) => `
<div class="col-md-2 container-fluid border justify-content-center" id="day-1">
<div class="text-center mt-2">
<p class="text-center m-0">${summaryItem.weatherSummary}
</p>
<p class="text-center m-0"><strong>${summaryItem.avgTemp}°</strong>
</p>
<img src="https://openweathermap.org/img/wn/${summaryItem.iconSummary}@2x.png" alt="" class="mx-auto">
<h5 class="text-center m-2">${summaryItem.dayName}</h5>
</div>
</div>
`;

const allDaysTemplate = `
<div class="row justify-content-center">
<h2>Five Day Forecast</h2>
</div>

<div class="row justify-content-center my-2">
${summaryData.map((summaryItem) => dayTemplate(summaryItem)).join("")}
</div>
`;

fiveDaySection.insertAdjacentHTML("beforeend", allDaysTemplate);
};

const searchButton = document.querySelector(".search");
searchButton.addEventListener("click", () => {
getNow();
getFiveDay();
document.querySelector("#query").value = "";
});