Coding weather app: simple and fun
You’ve got a user story with all that points she wants to have onboard. What’s your next step? Let’s ship the app as fast as we can. What we’ve got as a user story:
- I can see the weather in my current location.
- I can see a different icon or background image (e.g. snowy mountain, hot desert) depending on the weather.
- I can push a button to toggle between Fahrenheit and Celsius.
Design
Frameworks vs. Pure JS
The first thought was to dive into React or Vue to wrap my head around its basics. The second one was calm and clear: I have to implement it with the knowledge I’ve already got. All new libraries are the pleasure for a procrastinating mind, it’s not the time for it now. What about JQuery? But why? Current browser engines have plenty of functionality without extra wrappers. At least for initial prototyping step.
What is pure javascript? It is ES6 for now.
Minimum page element set
How to represent the current weather conditions? The distinctive photos of nature would fit best. But it’s for the next iteration, right now I want to get MVP. Let’s highlight temperature level. I would use a box for a text block colored according to the current temperature. I don’t want to invent temperature to color mapping — there is a bunch of existing examples in Google.
According to the user story, the app needs a toggle for different temperature scales. I want neither an extra preference tab nor a checkbox floating in nowhere. The idea is simple: a distinct temperature scale symbol would be used as a toggle. One single symbol is a poor UI experience especially in the light of accessibility requirements (see below). What if I put both letters for Celsius and Fahrenheit on the page together and make active vivid and another vague?
Accessibility
Does MVP need to be accessible? Definitely — yes. There are people with specific requirements. They deserve rich web experience that fulfills their perception.
Why accessibility is an important matter for me. I always pay close attention to color and texture decisions in visual media — billboards, product design. Often I catch myself on analysis of a color contrast in different media. Wish I had an app to wash out the color of the picture from a phone camera and to present you with another color world around. The world where almost 10% of the population lives. I’ve tried to find something like this in both mobile app stores but didn’t find any. There is a bunch of simple static color checker but I want something like vision simulator. Such an app needs modern frameworks for augmented reality — ARKit like…
Accessibility aspects of the page include visual, navigation, audible sections. The visual part is about color contrast and there are two great tools which help us. First is the desktop app that changes the color scheme of the screen to communicate different user features of vision. The second is the color contrast checker by Lea Verou. It measures the compliance of a contrast rate with accessibility recommendations by W3. The navigation part means that page is controllable by a keyboard. All control elements of the page are native HTML: scale toggles are radio buttons with labels. A user can hop between them with <tab> and press <Enter> or <Space> to make them act. The audible part includes aria-*
tags for all elements. It makes clear what scale is used now and how control element works. I used Chrome WAVE extension as an accessibility audit tool. There are a couple of tools to analyze web pages compliance.
Responsiveness
Responsive design means the page suits well for any screen or screenless devices. The first consideration here whether a content is flexible to fit a different screen resolution.
The second one is whether the page responds to user expectation on loading time. Chrome has built-in audit tool which shows exactly what makes a page load slow. In my case, it’s a usage of web fonts. Here is a great article by Zach Leatherman about web fonts optimization. Should I dive into this topic? A performance audit shows that the first paint of the page is within 1.5 seconds. It is a pretty decent result so why bother with optimization?
Through the plan of the implementation
Get user location
The first step is to get coordinates of the place where a user wants to know the weather. Only three lines of code and a user gets a popup prompting for geolocation permission.
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(success, error);
}
And there is a catch. What if a user refuses to share location, what if a user device fails to report it to the browser, what if the browser fails to process data — a lot of ifs… There are at least three mentions of the necessity of an error exception handling in the Paul Kinlan’s article. Let’s summarize.
Geolocation isn’t supported..? Add two lines more.
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(success, error);
} else {
// your steps to get user location
}
Browser/device failed to return location? Add another bunch…
if(navigator.geolocation) {
const location_timeout = setTimeout(function() {
// your steps to get user location
});
navigator.geolocation.getCurrentPosition(
function(success) {
clearTimeout(location_timeout);
// process location data
},
function(error) {
clearTimeout(location_timeout);
// your steps to get user location
};
} else {
// your steps to get user location
}
And the question is still here: what should fill into // your steps to get user location
gap. There are at least two ways: ask a user to enter location explicitly and get it by other location services connected. I choose the second and address it in “Challenges”.
API to get weather
I’ve got user location and now the time for a weather API to come. My choice is Weather Underground. It has (and OpenWeatherMap hasn’t) so-called “feels like” temperature. Yeah, sounds like a sly trick but it has an evidence base. And it’s more way more connected to the weather perception. For more information take a look at Wiki’s “Heat index” and “Wind chill” articles.
As long as I use ES6 the natural way to get data is fetch
.
fetch(httpAddr)
.then(resp => {
if (resp.ok) return resp.json();
throw new Error();
})
.then(data => {
// process JSON data
})
.catch (error => console.error(`Error - ${error.msg}, ${error.stack}`));
But there is a catch again — how to provide support for older browsers? See “Challenges” section below.
We’ve got fetch
as a vehicle let’s find what to carry. Our httpAddr
is empty and in order to fill it, we need to construct URL as described in API documentation. The first step is to get a key. Weather Underground’s description how to do it is here (for OpenWeatherMap key check the section of their API manual).
httpAddr = 'http://api.wunderground.com/api/Your_Key/conditions/q/CA/San_Francisco.json';
fetch(httpAddr)
.then(resp => {
if (resp.ok) return resp.json();
throw new Error();
})
.then(data => {
// process JSON data
})
.then(data => {
// display data
}
.catch (error => console.error(`Error - ${error.msg}, ${error.stack}`));
Visual elements of the page
The color of the box where weather details appear would change according to the current air temperature. I have temperature value in JSON data from fetch
so now it’s just a mapping in dozen lines of code:
function getWeatherColor(temp_k) {
let colors = {};
if (303.15 <= temp_k) {
colors = { main: '#A50021', contrast: '#fff' };
// ...
// additional lines for mapping temperature
// between 258.15 and 303.15 Kelvin or
// between -15 and 30 Celsius or
// between 5 and 86 Fahrenheit.
// ...
} else if (253.15 <= temp_k && temp_k < 258.15) {
colors = { main: '#264CFF', support: '#fcc'};
}
return colors;
}
My choice for color mapping is in the pen.
User preferences
Almost finished except the last touch. I don’t want to toggle scale from Fahrenheit to Celsius or vice versa all the time I run the app. Let’s keep the user scale preferences in a persistent storage. The simplest and oldest storage are browser cookies. There are three pieces of code to create, read and erase cookie. I modified readCookies
to use a regular expression instead of a loop.
The app is ready to ship so here it is. And now let’s talk about challenges.
Challenges
Geolocation on mobiles
After I finished initial version of the page I tested it in Chrome, Firefox, Opera, and Safari. It worked great in the first three and throws an unknown geolocation error in Safari. My thought was: Ok, let’s skip it for a while and switch to mobile devices. I tested the app on an iPad, an iPhone, and an Android phone, with a range of browsers: Safari, Chrome, Firefox. The only combination that worked was Android + Firefox. All other throws a geolocation timeout error. I browsed for a little and found only that it’s not only my problem and there was a bunch of advice. The first question I had to answer was whether I worked within secured HTTP or not. I deployed the app on two different services — github.io and codepen.io, both provide secured HTTP. I’ve tried following bits of advice:
- use
PositionOptions.maximumAge
set to Infinity; - use
PositionOptions.timeout
set to long intervals; - use
PositionOptions.enableHighAccuracy
either true or false; - use
watchPosition
instead ofgetCurrentPosition
.
None of these worked for me. I decided to add geoIP request as a fallback. Every time the app can’t get the location from a user device it asks the location of device IP-address assigned. As a free secured engine, I used geoIP API. It’s straightforward and doesn’t require any registration or keys to work.
Support for a wide range of browsers
During the initial attempt, I succeed in implementing a working prototype of the app. It worked in Chrome and Firefox (last versions) on a desktop platform and didn’t work in Safari (due to the lack of support of fetch
before Safari 10.3) and on mobile (weird quirks with navigator.geolocation
mentioned above). It's not the way I wanted it to be, so I added babel
preprocessing for JS and fetch
polyfill to address older Safari troubles.
And I moved my project to GitHub to get a standalone app. The last step required to convert plain single HTML file to gulp-powered workflow (pretty decent description how to start with gulp). The reason is to get:
- ES6 features work in older browsers with babel — https://babeljs.io/;
- browser specific CSS prefixes with autoprefixer — https://github.com/postcss/autoprefixer;
- fetch polyfill installed and attached as an npm module — https://github.com/github/fetch;
- browser-sync to monitor and reload app in browsers on any change;
My gulp configuration is available on GitHub.
P.S.
MVP is dull. I throw in another bunch of code to add photo background animated according to the current weather condition. All photos are taken from Flickr. They are all under Creative Commons licenses.
Find me on: