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
79 changes: 79 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
/>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
<title>Weather App</title>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="page-header mt-4">
<h1>Weather Project</h1>
</div>
<hr class="page-divider" />

<form id="search-form" class="mb-4">
<div class="form-row container-fluid">
<div class="col-6 offset-md-1">
<input
type="text"
id="search-query"
class="form-control"
placeholder="Enter the City Name Here"
/>
</div>
<div class="col-2">
<button type="submit" class="btn btn-primary btn-block search">
Search City
</button>
</div>

<div class="col-2">
<button
type="submit"
class="btn btn-primary btn-block set-default"
>
Set as Default
</button>
</div>
</div>
</form>

<div class="container-fluid">
<div class="weather-data offset-md-2"></div>
</div>
<div class="mt-5 col-4 offset-md-4 text-center">
<button type="submit" class="btn btn-primary btn-block geo-locate">
Weather at my Current Location
</button>
<div
id="spinner"
class="loader offset-md-5 mt-5"
style="display: none"
></div>
</div>
</div>
</div>
</div>

<div class="col-12 d-flex justify-content-center mt-5">
<div id="map" style="width: 600px; height: 450px"></div>
</div>

<script
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDbcmkOz0m2H2Eo5eum8Cm61U4jWCrn6rg&callback=initMap&v=weekly&libraries=marker"
defer
></script>

<script src="main.js" type="text/javascript"></script>
</body>
</html>
300 changes: 300 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
let currentWeather = [];
let weatherForecast = [];

//Render on Page Reload, etc.
if (localStorage.defaultWeather) {
let savedWeather = localStorage.defaultWeather;

document.querySelector(".weather-data").innerHTML =
`<div class="text-left mb-5 default-city"><strong>DEFAULT CITY</strong></div>` +
savedWeather;
}

const renderWeatherData = () => {
const weather = currentWeather[0];

//Daily Data Array
const dailyData = weatherForecast[0].map((_, i) => ({
condition: weatherForecast[0][i],
temp: weatherForecast[1][i],
icon: weatherForecast[2][i],
day: weatherForecast[3][i],
}));

//Change Styling Based on Current Weather w/ Switch Statement
switch (weather.condition) {
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 can be extracted to a method, no need to redefine it every time you call this function

case "Clouds":
document.body.style.backgroundColor = "#80808080";
break;
case "Clear":
document.body.style.backgroundColor = "#d4ef3a80";
break;
case "Mist":
document.body.style.backgroundColor = "#647eef80";
break;
case "Rain":
document.body.style.backgroundColor = "#0e0e8080";
break;
case "Thunderstorm":
document.body.style.backgroundColor = "#87070780";
break;
case "Drizzle":
document.body.style.backgroundColor = "#89CFF080";
break;
case "Haze":
document.body.style.backgroundColor = "#C4A48480";
break;
default:
document.body.style.backgroundColor = "white";
}

let currentTemplate = `<div class = "row current-weather mb-4">
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can be const

<div class = "current-conditions col-md-3 offset-md-2 text-center"><strong>
<div>${Math.round(weather.temp)}°F</div>
<div>${weather.city}</div>
<div>${weather.condition}</div></strong>

</div>
<div class="current-icon col-md-2" >
<img src="https://openweathermap.org/img/wn/${weather.icon}@2x.png" class="condition ${weather.condition}" alt="${weather.condition}">
</div>

</div>

`;

const forecastHTML = dailyData
.map(
(item) => `
<div class="col-md-2 text-center day"><strong>
<div>${item.condition}</div>
<div>${item.temp}</div>
<img src="https://openweathermap.org/img/wn/${item.icon}@2x.png" class="img-fluid" alt="${item.condition}">
<div>${item.day}</div>
</strong>
</div>

`,
)
.join("");

document.querySelector(".weather-data").innerHTML =
currentTemplate +
`<div class="row forecast-data text-center">${forecastHTML}</div>`;
};

//Event Listener for Geolocation
const spinner = document.getElementById("spinner");

document.querySelector(".geo-locate").addEventListener("click", (e) => {
e.preventDefault();

spinner.style.display = "block";

//Geolocation with Error Handling

navigator.geolocation.getCurrentPosition(
(position) => {
lat = position.coords.latitude;
lon = position.coords.longitude;

fetchWeather(lat, lon);
},
(error) => {
spinner.style.display = "none";
alert(
"Either Location Access Denied or timed out. Refresh the Page and Try Again",
);
},
{ timeout: 7000 },
);
});

//Event Listener when clicking on map

//Event Listener for Map

let map;
let clickMarker;

function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 36.2371, lng: -79.9795 },
zoom: 12,
});

if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
const initialPos = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};

updateWeatherByMap(initialPos.lat, initialPos.lng);
});
}

map.addListener("click", (e) => {
const lat = e.latLng.lat();
const lon = e.latLng.lng();

updateWeatherByMap(e.latLng.lat(), e.latLng.lng());
});
}

//Map Helper Function
updateWeatherByMap = (lat, lon) => {
const pos = { lat, lng: lon };

map.panTo(pos);

if (clickMarker) {
clickMarker.setMap(null);
}

clickMarker = new google.maps.Marker({
position: pos,
map: map,
title: "Selected Location",
});

fetchWeather(lat, lon);
};

//Event Listener for Set Default
document.querySelector(".set-default").addEventListener("click", (e) => {
e.preventDefault();

const weatherData = document.querySelector(".weather-data");

const label = weatherData.querySelector(".default-city");
if (label) {
label.remove();
}

const field = weatherData.innerHTML;
localStorage.setItem("defaultWeather", field);

weatherData.innerHTML =
`<div class="text-left mb-5 default-city"><strong>DEFAULT CITY</strong></div>` +
localStorage.defaultWeather;
});

//Event Listener for Search
document.querySelector(".search").addEventListener("click", (e) => {
e.preventDefault();

const city = document.querySelector("#search-query").value;

fetchWeather(city);

document.querySelector("#search-query").value = "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if requests fails, this is a bad experience

});

const addCurrentWeather = (data) => {
currentWeather = [];

const conditions = {
temp: (data.main.temp - 273.15) * (9 / 5) + 32,
city: data.name || null,
condition: data.weather[0].main || null,
icon: data.weather[0].icon || null,
};

currentWeather.push(conditions);
};

const addForecast = (data) => {
weatherForecast = [];

const weatherArray = [];
const tempArray = [];
const iconArray = [];
const dayArray = [];

let count = 0;
let subWeather = [];
let subTemp = [];
let subIcon = [];
let subDay = [];

for (let i = 0; i < data.list.length; i++) {
count += 1;

subWeather.push(data.list[i].weather[0].main);
subTemp.push(
`${Math.round((data.list[i].main.temp - 273.15) * (9 / 5) + 32)}°F`,
);
subIcon.push(data.list[i].weather[0].icon);
const date = new Date(data.list[i].dt_txt);
subDay.push(date.toLocaleDateString("en-US", { weekday: "long" }));

if (count === 8) {
weatherArray.push(mode(subWeather));
tempArray.push(mode(subTemp));
iconArray.push(mode(subIcon));
dayArray.push(mode(subDay));

count = 0;
subWeather = [];
subTemp = [];
subIcon = [];
subDay = [];
}
}

weatherForecast.push(weatherArray);
weatherForecast.push(tempArray);
weatherForecast.push(iconArray);
weatherForecast.push(dayArray);
};

//Find the mode of each nested Array
const mode = (array) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

not clear what mode is referring to.

frequencyObject = {};

array.forEach((number) => {
frequencyObject[number] = (frequencyObject[number] || 0) + 1;
});

const mode = Object.entries(frequencyObject).sort(([, a], [, b]) => b - a);

return mode[0][0];
};

const fetchWeather = (...args) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

its not good to pass args like this with 2 different types of arguments.
Define both and then check if passed, or wrap in a different method to make it more readable.

Suggested change
const fetchWeather = (...args) => {
const fetchWeather = (city, coordinates) => {

let url;

if (args.length === 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.

before this, need to trim the args.

url = `https://api.openweathermap.org/data/2.5/weather?q=${args[0]}&appid=d408f27efbf2eb54ef2bd39871d4fe8b`;
} else {
url = `https://api.openweathermap.org/data/2.5/weather?lat=${args[0]}&lon=${args[1]}&appid=d408f27efbf2eb54ef2bd39871d4fe8b`;
}

//GET Request by Default; Includes Error Handling
fetch(url)
.then((data) => {
if (!data.ok) throw new Error("City not found. Please re-enter.");
return data.json();
})
.then((data) => {
addCurrentWeather(data);

const urlForecast = `https://api.openweathermap.org/data/2.5/forecast?lat=${data.coord.lat}&lon=${data.coord.lon}&appid=d408f27efbf2eb54ef2bd39871d4fe8b`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

if data changed, your code will crash


return fetch(urlForecast);
})
.then((res) => res.json())
.then((forecastData) => {
addForecast(forecastData);

renderWeatherData();
})

.catch((error) => {
alert(error.message);
})
.finally(() => {
spinner.style.display = "none";
});
};
Loading