Skip to content

Instantly share code, notes, and snippets.

@qodesmith
Created September 9, 2018 22:29
Show Gist options
  • Save qodesmith/c0ae06a71c6f366779d6506ba31f8593 to your computer and use it in GitHub Desktop.
Save qodesmith/c0ae06a71c6f366779d6506ba31f8593 to your computer and use it in GitHub Desktop.
Weather App
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<!-- Place favicon.ico in the root directory -->
<link rel="stylesheet" href="resources/css/normalize.css">
<link rel="stylesheet" href="resources/css/style.css">
<link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
</head>
<body>
<h1>Weather Now</h1>
<form class="weather-search">
<input type="text" name="city" placeholder="Enter a U.S. city"></input>
<button id="go" type="submit">GO</button>
</form>
<main id="weather-results"></main>
<script src="resources/js/main2.js"></script>
</body>
</html>
const locationUrl = 'https://dataservice.accuweather.com/locations/v1/cities/search';
const currentUrl = 'https://dataservice.accuweather.com/currentconditions/v1/';
const forecastUrl = 'https://dataservice.accuweather.com/forecasts/v1/daily/5day/';
const api = '?apikey=dWD7IznmsGfuKaKFufT5l9vOGUgNiiQG';
// The only DOM reference we need :)
const weatherResults = document.querySelector('#weather-results');
/*
Adding the event listener to the form instead.
Doing so gives us access to some features the form comes with out of the box.
If there are any named fields in the form, we have access to those on the event object.
*/
document.querySelector('.weather-search').addEventListener('submit', e => {
/*
Prevent the form from doing it's ancient default HTML behavior
which is to try to submit data somewhere and reload the page.
No thanks. We know what we're doing. We'll take it from here.
*/
e.preventDefault();
/*
The input field is named "city" so we can access its value
like so *because* our listener is on the form.
`e.target` is the form, and HTML5 forms will put any named fields
inside the form on the `e.target` object like below.
*/
const location = e.target.city.value;
// Dynamically construct the url for the weather api.
const newLocationUrl = `${locationUrl}${api}&q=${location}`;
// Begin a series of AJAX requests to the weather API via fetch.
fetch(newLocationUrl)
.then(res => res.json())
.then(data => {
const locationKey = data[0].Key;
const newCurrentUrl = `${currentUrl}${locationKey}${api}`;
const newForecastUrl = `${forecastUrl}${locationKey}${api}`;
/*
This object will serve to collect all the data we want to put on the screen.
As you'll see below, we use the spread (...) operator to update this objects
contents with each fetch's response.
*/
let templateData = {
city: data[0].LocalizedName,
state: data[0].AdministrativeArea.LocalizedName
};
/*
Since we have 2 more calls to the weather API that don't depend on
eachothers results, we can use `Promise.all` to resolve them.
*/
const promise1 = fetch(newCurrentUrl)
.then(res => res.json())
.then(data => {
const iconNum = data[0].WeatherIcon
templateData = {
...templateData,
temp: data[0].Temperature.Imperial.Value,
desc: data[0].WeatherText,
iconClass: `wi wi-${iconClass(iconNum)}`
};
});
const promise2 = fetch(newForecastUrl)
.then(res => res.json())
.then(data => {
const days = [0,1,2,3,4].map(num => ({
high: data.DailyForecasts[num].Temperature.Maximum.Value,
low: data.DailyForecasts[num].Temperature.Minimum.Value
}));
templateData = { ...templateData, days };
});
/*
Remember, `fetch` and `.then` are nothing more than promises themselves.
So by storing them in variables above, we're storing the promises themselves,
not their return values. That's the reason we need to chain another `.then`
to `Promise.all` - which is also a promise!
*/
Promise.all([promise1, promise2]).then(() => {
weatherResults.innerHTML = htmlTemplate(templateData);
})
});
});
/*
There's no way around it - there's a lot of conditions we have to consider.
We can reduce the amount of code needed by simply declaring `if` statements
that each return a value. Remember, the `return` statement will both
spit out a value and prevent any code below it from executing.
*/
function iconClass(num) {
if (num <= 5) return 'day-sunny';
if (num <= 8) return 'day-cloudy';
if (num === 11) return 'fog';
if (num > 11 && num <= 14) return 'day-showers';
if (num <= 17) return 'thunderstorm';
if (num === 18) return 'rain';
if (num <= 21) return 'snowflake-cold';
if (num <= 29) return 'snow';
if (num === 30) return 'hot';
if (num === 31) return 'cold';
if (num === 32) return 'windy';
if (num <= 34) return 'night-clear';
if (num <= 38) return 'cloudy';
if (num <= 42) return 'showers';
return 'snow'
}
/*
One of the best features to hit JavaScript in ES6 was template literals!
As you know, we can use backticks to declare strings and interpolate
expressions inside the ${}. Using this functionality, we can create a
template that will be used to set the innerHTML of something in the DOM.
Looks pretty similar to the JSX in a React components render method, right?
*/
function htmlTemplate(data) {
return `
<h2>${data.city}, ${data.state}</h2>
<div>
<div id="current-temp">
<i class="${data.iconClass}"></i>
<div class="temp">${data.temp}&deg;F ${data.desc}</div>
</div>
<section class="five-day">
${
data.days
.map(day => (
`
<div class="days">
<span class="high">${day.high}</span>
<span class="low">${day.low}</span>
</div>
`
)).join('')
}
</section>
</div>
`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment