1
\$\begingroup\$

I'm making a WeatherApp. It uses the browser's location to get the GPS coordinates, then sends those to an API to get details about the location (e.g. city, country, etc.). The city is then sent to a weather API to get weather information, which is then displayed on the page - see the image below for a sample.

But I thinks there're a lot of ways to make it less clumsy. If you have some advice, you are welcome)

What's a better way to make "universal" widget?

While the code functions in the snippet, there is a security restriction. It does, however, work in this codepen. It should output data to widget: Widget on Codepen

// (1) There I get geolocation with two functions in case of success or error
navigator.geolocation.getCurrentPosition(success, error);
function success(position) {
 const locationToken = '6efc940a8a8a31';
 
 let lat = position.coords.latitude;
 let lon = position.coords.longitude;
 let locRequest = new XMLHttpRequest();
 // (2) I use LocationIq to get the City name by geolocatioan coordinates
 locRequest.open(
 'GET',
 `https://eu1.locationiq.com/v1/reverse.php?key=${locationToken}&lat=${lat}&lon=${lon}&format=json`,
 true
 );
 locRequest.send();
 locRequest.onreadystatechange = function() {
 if (this.readyState != 4) return;
 if (this.status != 200) {
 alert('Error: ' + (this.status ? this.statusText : 'request aborted'));
 return;
 }
 // (3) Okay, I got the city name and pass it to another function to get the weather in that city
 let data = JSON.parse(this.responseText);
 getWeather(data.address.city);
 }
};
function error() {
 console.log = "Unable to retrieve your location";
};
function getWeather(location) {
 const weatherToken = 'bce14d2969489e270e45aaf51514f3a8';
 let weatherLocation = location;
 let weatherRequest = new XMLHttpRequest();
 // (4) I send a request to OpenWeather using the City name
 weatherRequest.open(
 'GET',
 `https://api.openweathermap.org/data/2.5/weather?q=${weatherLocation}&units=metric&appid=${weatherToken}`,
 true
 );
 weatherRequest.send();
 weatherRequest.onreadystatechange = function() {
 if (this.readyState != 4) return;
 if (this.status != 200) {
 alert('Error: ' + (this.status ? this.statusText : 'request aborted'));
 return;
 }
 let weather = JSON.parse(this.responseText);
 // (5) and, finally, I pass all weather data to my widget
 weatherWidget(weather);
 }
}
function weatherWidget(data) {
 let allData = data;
 let currentLocation = document.querySelector('.js-currentLocation');
 let temp = document.querySelector('.js-temp');
 let weatherType = document.querySelector('.js-weatherType');
 let weatherPreview = document.querySelector('.js-widgetPrev');
 // (6) Here I pass necessary data to my Widget
 currentLocation.innerHTML = allData.name;
 temp.innerHTML = `${allData.main.temp}°C`;
 weatherType.innerHTML = allData.weather[0].main;
 let weatherPrevClass = allData.weather[0].main.toLowerCase();
 weatherPreview.classList.add(weatherPrevClass);
}
:root {
 --widget-size: 400px;
 --widget-padding: 50px;
 --widget-border-rad: 40px;
}
html,
body {
 height: 100%;
}
body {
 // background-color: #f9f8f8;
 background-image: linear-gradient(to right bottom, #89f6df, #8ef9cb, #9dfab4, #b4f999, #d0f67f);
 display: flex;
 align-items: center;
 justify-content: center;
 font-family: 'Open Sans', sans-serif;
}
.widget {
 position: relative;
 width: var(--widget-size);
 height: var(--widget-size);
 color: #fff;
 border-radius: var(--widget-border-rad);
 background-color: rgba(255, 255, 255, .7);
 box-shadow: 0 19px 38px rgba(0, 0, 0, 0.20), 0 15px 12px rgba(0, 0, 0, 0.15);
 background-image: linear-gradient(to left top, #ffc94d, #ffb93a, #ffa729, #ff9518, #ff8208);
 &__info {
 position: absolute;
 bottom: var(--widget-padding);
 left: var(--widget-padding);
 }
 &__temp {
 font-size: 6em;
 line-height: 1em;
 }
 &__desc {
 position: absolute;
 top: var(--widget-padding);
 left: var(--widget-padding);
 font-size: 2.5em;
 font-weight: 700;
 }
 &__currentLocation {
 // text-align: center;
 font-size: 2em;
 }
 &__prev {
 position: absolute;
 top: var(--widget-padding);
 right: var(--widget-padding);
 }
}
// ---> Sunny
.sun {
 width: 100px;
 height: 100px;
 background-color: #fdde35;
 border-radius: 50%;
 position: relative;
}
.sun::after {
 content: '';
 display: block;
 position: absolute;
 width: inherit;
 height: inherit;
 border-radius: inherit;
 background-color: inherit;
 animation: sunshines 2s infinite;
}
@keyframes sunshines {
 0% {
 transform: scale(1);
 opacity: 0.6;
 }
 100% {
 transform: scale(1.5);
 opacity: 0;
 }
}
// ---> Cloudy
.clouds {
 margin-top: 40px;
 position: relative;
 height: 60px;
 width: 140px;
 background-color: #fff;
 border-radius: 30px;
 animation: clouds 2.5s infinite ease-in-out;
 &::after {
 content: '';
 display: block;
 position: absolute;
 width: 60px;
 height: 60px;
 background-color: inherit;
 border-radius: 50%;
 left: 25px;
 top: -35px;
 }
 &::before {
 content: '';
 display: block;
 position: absolute;
 width: 50px;
 height: 50px;
 background-color: inherit;
 border-radius: 50%;
 left: 70px;
 top: -25px;
 }
}
@keyframes clouds {
 50% {
 transform: translateY(7px);
 }
 100% {
 transform: translateY(0px);
 }
}
<div class="widget">
 <div class="widget__desc js-weatherType"></div>
 <div class="widget__info">
 <div class="widget__temp js-temp"></div>
 <div class="widget__currentLocation js-currentLocation"></div>
 </div>
 <div class="widget__prev">
 <div class="js-widgetPrev">
 </div>
 </div>
</div>

Sᴀᴍ Onᴇᴌᴀ
29.5k16 gold badges45 silver badges201 bronze badges
asked Oct 29, 2018 at 11:40
\$\endgroup\$
2
  • \$\begingroup\$ You could add a description of the output, perhaps with a sample screenshot. I tried running the codepen but see the Error message, since the query to https://api.openweathermap.org/data/2.5/weather yields no results... perhaps because of the q parameter in the query string is undefined: q=undefined&units=metric&... \$\endgroup\$ Commented Oct 29, 2018 at 16:27
  • \$\begingroup\$ @SᴀᴍOnᴇᴌᴀ, oh, thanks. I will do it for sure, but OpenWeather is not available for a while. I will do refactoring tomorrow and update my question. Thanks, again. \$\endgroup\$ Commented Oct 29, 2018 at 16:38

1 Answer 1

1
\$\begingroup\$

There appears to be a flaw with the function error:

function error() {
 console.log = "Unable to retrieve your location";
};

That will re-assign that string literal to console.log. Any subsequent calls to console.log() would then throw a TypeError:

console.log is not a function

Presumably you meant to call that method and pass the string literal:

console.log("Unable to retrieve your location");

There is also console.error() which could be used in this case:

console.error("Unable to retrieve your location");


When I try running the codepen on my office computer, the location data coming back from the locationIQ API does not contain a city property in the address property - see the sample below (also, it is about 3 kilometers away from my actual location - perhaps my ISP has equipment at that spot). You should consider an alternative property when this happens.

{
 "place_id": "159393810",
 "licence": "© LocationIQ.com CC BY 4.0, Data © OpenStreetMap contributors, ODbL 1.0",
 "osm_type": "way",
 "osm_id": "368233107",
 "lat": "45.50757005",
 "lon": "-122.6897144171",
 "display_name": "1017, Southwest Myrtle Drive, Portland Heights, Southwest Hills, Multnomah County, Oregon, 97201, USA",
 "address": {
 "house_number": "1017",
 "road": "Southwest Myrtle Drive",
 "neighbourhood": "Portland Heights",
 "suburb": "Southwest Hills",
 "county": "Multnomah County",
 "state": "Oregon",
 "postcode": "97201",
 "country": "USA",
 "country_code": "us",
 "town": "Southwest Hills"
 },
 "boundingbox": [
 "45.5074436",
 "45.5077148",
 "-122.6899028",
 "-122.6895124"
 ]
}

I was able to see weather information for my city using the postcode, which still displays the name of my city:

getWeather(data.address.postcode);

Though some would argue that a safer check would be to ensure that the property exists before using it.


I suggest you look into using Promises for XHR requests. There are APIs like the Fetch API, or others like reqwest and axios. Or as the MDN documentation illustrates, promises can be used to wrap up XHR code. Because you are using features, you could also consider async and await.


Many of the lines of code use let for values that never get re-assigned. For example, the following two lines appear at the end of the callback function assigned to locRequest.onreadystatechange:

let data = JSON.parse(this.responseText);
getWeather(data.address.city);

Because data is never re-assigned, const would be appropriate and avoid any potential re-assignment. There are some who recommend the following:

  • use const by default
  • only use let if rebinding is needed 1

Consider the DOM queries:

let currentLocation = document.querySelector('.js-currentLocation');
let temp = document.querySelector('.js-temp');
let weatherType = document.querySelector('.js-weatherType');
let weatherPreview = document.querySelector('.js-widgetPrev');

If those elements are really unique, then it would be advisable to update the HTML to use an id attribute for those single elements:

<div class="widget">
 <div class="widget__desc" id="js-weatherType"></div>
 <div class="widget__info">
 <div class="widget__temp" id="js-temp"></div>
 <div class="widget__currentLocation" id="js-currentLocation"></div>
 </div>
 <div class="widget__prev">
 <div id="js-widgetPrev">
 </div>
 </div>
</div>

Then those elements can be selected by id:

const currentLocation = document.querySelector('#js-currentLocation');
const temp = document.querySelector('#js-temp');
const weatherType = document.querySelector('#js-weatherType');
const weatherPreview = document.querySelector('#js-widgetPrev');

And then use document.getElementById() because it typically performs faster (see this performance comparison test) than document.querySelector(), which supports many more selectors

const currentLocation = document.getElementById('js-currentLocation');
const temp = document.getElementById('js-temp');
const weatherType = document.getElementById('js-weatherType');
const weatherPreview = document.getElementById('js-widgetPrev');

1https://mathiasbynens.be/notes/es6-const

answered Oct 30, 2018 at 15:27
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.