Skip to content
Open
Show file tree
Hide file tree
Changes from 17 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
key.js
countries-cities.json
57 changes: 57 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!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/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"
/>
<link rel="stylesheet" href="style.css" />
</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>



Copy link
Copy Markdown

Choose a reason for hiding this comment

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

beware of empty spaces

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

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
Copy Markdown
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";

const getCurrentLocation = () => {
const options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
};

return new Promise((resolve, reject) => {
Copy link
Copy Markdown

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(
(pos) => {
resolve({
lon: pos.coords.longitude,
lat: pos.coords.latitude,
});
},
(error) => reject(console.warn(`ERROR(${error.code}): ${error.message}`)),
options
);
});
};

async function fetchCurrentLocationData(location) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

some functions are es6, others es5, be consistent

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;
}

async function addCurrentLocation() {
try {
const location = await getCurrentLocation();
const locationData = await fetchCurrentLocationData(location);
addNow(locationData);

const fiveDayData = await fetchFiveDayData(location);
const processedData = await processFiveDayData(fiveDayData);
addFiveDay(processedData);

addFiveDay;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

seems like an accident? Should delete

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

oh do you mean to return it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yeah that's an accident. I deleted.

} catch (error) {
console.error("Error processing data:", error);
Copy link
Copy Markdown

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();

async function getNow() {
try {
const nowData = await fetchNow();
addNow(nowData);
} catch (error) {
console.error("Error processing data:", error);
}
}

async function fetchNow() {
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;
}

async function addNow(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);
}

async function getFiveDay() {
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);
}
}

async function fetchCoordinates() {
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;
}

async function fetchFiveDayData(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, {
Copy link
Copy Markdown

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;
}

async function processFiveDayData(data) {
// create an array for each of the five days

const dividedDayData = [];

// process information in each array to produce the correct temperature estimate and the most commonly listed icon code and the name of the day. This information could be stored in an object that summarizes the information. This would make it easier to reference in the html tempalate.

// update html template with each day's information

function processDayData(day) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this function is too big, needs to be spread into smaller functions to help with readability and maintainability

const dayData = {
avgTemp: 0,
iconSummary: null,
weatherSummary: null,
dayName: "",
};

let tempAcc = 0;
let icons = {};
let weatherDescriptions = {};

// identify the appropriate data range
let indexRange = [];

if (day === 1) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe just make this a dictionary lookup

Copy link
Copy Markdown

@greypants greypants Dec 6, 2024

Choose a reason for hiding this comment

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

Yeah, you could store this mapping in an object like this:

const ranges = {
  1: [0, 7],
  2: [0, 15],
  ...etc
}

const indexRange = ranges[day];

However, lines 168-184 could be replaced with one line with simple math...

I'll hold off on giving you the solution... but I have it :D

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Figured this out!

indexRange = [0, 7];
}
if (day === 2) {
indexRange = [8, 15];
}
if (day === 3) {
indexRange = [16, 23];
}
if (day === 4) {
indexRange = [24, 31];
}
if (day === 5) {
indexRange = [32, 39];
}

// loop through the identified data range to accumulte data
for (let i = indexRange[0]; i <= indexRange[1]; i++) {
Copy link
Copy Markdown

@tommymarshall tommymarshall Dec 6, 2024

Choose a reason for hiding this comment

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

it's really good do the hasOwnProperty check! that being said, this is a shorter way of writing it, since we essentially do the same thing twice wutgh

for (let i = indexRange[0]; i <= indexRange[1]; i++) {
  // once you understand this, half of your javascript knowledge is complete
  // the `[{ main, icon }]` will only ever take the first item in the array, so it's sort of brittle
  const { main: { temp }, weather: [{ main, icon }] } = data.list[i];
  
  tempAcc += temp;
  weatherDescriptions[main] = (weatherDescriptions[main] || 0) + 1;
  icons[icon] = (icons[icon] || 0) + 1;
}

const time = data.list[i];

// accumulate temperature values to calculate average after the fact
tempAcc += time.main.temp;

// accumulate weather descriptions
if (weatherDescriptions.hasOwnProperty(time.weather[0].main)) {
weatherDescriptions[time.weather[0].main]++;
} else {
weatherDescriptions[time.weather[0].main] = 1;
}

// accumulate weather icons
if (icons.hasOwnProperty(time.weather[0].icon)) {
icons[time.weather[0].icon]++;
} else {
icons[time.weather[0].icon] = 1;
}
}

// calculate average by dividing accumulated temperatures by 8
dayData.avgTemp = (tempAcc / 8).toFixed(1);

// find the day of the week by creating a new Date, and the processessing that with Intl.DateTimeFormat.

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

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

dayData.dayName = dateFormatter.format(dt_txt);

// find most common weather description. if all weather descriptions are equal, leave the first one.
let weatherDescriptionsMaxCount = 0;

for (let key in weatherDescriptions) {
if (weatherDescriptions[key] > weatherDescriptionsMaxCount) {
weatherDescriptionsMaxCount = weatherDescriptions[key];
dayData.weatherSummary = key;
}
}

// find most common icon. if all icons are equal, leave the first icon.
let iconMaxCount = 0;

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

return dayData;
}

//loop through all days to return a summary data object or each day
for (let i = 1; i <= 5; i++) {
const processedDayData = processDayData(i);
dividedDayData.push(processedDayData);
}

return dividedDayData;
}

async function addFiveDay(summaryData) {
const fiveDaySection = document.querySelector(".forecast-5-day");
fiveDaySection.replaceChildren();

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


<div class="row justify-content-center my-2">
<div class="col-md-2 container-fluid border justify-content-center" id="day-1">
<p class="text-center mt-2">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You could make a function that is a template of just the day of summary data and then iterate over it. Would be a lot less code and you would repeat it less

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Did this!

<span class="text-center">${summaryData[0].weatherSummary}</span>
<br />
<span class="text-center"><strong>${summaryData[0].avgTemp}°</strong></span>
<br />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nit: no br do it with CSS 😈

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I changed to using

and used bootstrap classes to handle spacing.

<img src="https://openweathermap.org/img/wn/${summaryData[0].iconSummary}@2x.png" alt="" />
<br />
<span>${summaryData[0].dayName}</span>
</p>
</div>

<div class="col-md-2 container-fluid border justify-content-center" id="day-2">
<p class="text-center mt-2">
<span class="text-center">${summaryData[1].weatherSummary}</span>
<br />
<span class="text-center"><strong>${summaryData[1].avgTemp}°</strong></span>
<br />
<img src="https://openweathermap.org/img/wn/${summaryData[1].iconSummary}@2x.png" alt="" />
<br />
<span>${summaryData[0].dayName}</span>
</p>
</div>

<div class="col-md-2 container-fluid border justify-content-center" id="day-3">
<p class="text-center mt-2">
<span class="text-center">${summaryData[2].weatherSummary}</span>
<br />
<span class="text-center"><strong>${summaryData[2].avgTemp}°</strong></span>
<br />
<img src="https://openweathermap.org/img/wn/${summaryData[2].iconSummary}@2x.png" alt="" />
<br />
<span>${summaryData[2].dayName}</span>
</p>
</div>

<div class="col-md-2 container-fluid border justify-content-center" id="day-4">
<p class="text-center mt-2">
<span class="text-center">${summaryData[3].weatherSummary}</span>
<br />
<span class="text-center"><strong>${summaryData[3].avgTemp}°</strong></span>
<br />
<img src="https://openweathermap.org/img/wn/${summaryData[3].iconSummary}@2x.png" alt="" />
<br />
<span>${summaryData[3].dayName}</span>
</p>
</div>

<div class="col-md-2 container-fluid border justify-content-center" id="day-5">
<p class="text-center mt-2">
<span class="text-center">${summaryData[4].weatherSummary}</span>
<br />
<span class="text-center"><strong>${summaryData[4].avgTemp}°</strong></span>
<br />
<img src="https://openweathermap.org/img/wn/${summaryData[4].iconSummary}@2x.png" alt="" />
<br />
<span>${summaryData[4].dayName}</span>
</p>
</div>
</div>
`;

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

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