Coding weather app: simple and fun

Photo credit — Nicolas Raymond

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?

Photo credit — Kay Gaensler

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.

Photo credit — Martin Fisch

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 of getCurrentPosition.

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:

My gulp configuration is available on GitHub.

Photo credit — Jim Choate

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.

Codepen link.

Find me on:

Blog

Twitter

FreeCodeCamp