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>

\$\endgroup\$
  • \$\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\$ – Sᴀᴍ Onᴇᴌᴀ Oct 29 '18 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\$ – purplecandle Oct 29 '18 at 16:38
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

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.