Chapter 14 - Tying it All Together!

So far we have written some pretty code, but we can do better than this in terms of readability. Also, there are a few issues here with the scope of some of our functions. As of now, when we load our code, the end user, or the consumer (the entity that's meant to use our app -- could be a person, or could be another application as we've seen with APIs) has access to all the intermediate functions in our application. The thing is, though, we don't want our end user to have access to anything other than our interface functions -- weatherForMyLocation and weatherForZipCodes If they have access to other functions, they might break the app, or not understand how it is supposed to work, and in turn, not get the correct data back from it. No good. So how can we "hide" the other functions from our users and restrict access to only the two interface functions? With scoping!

Closures

Remember when we talked briefly about scopes when I said functions in JS create their own scope and then the variables declared inside their curly braces is only available to them?

So check this out:

var obfuscatedSecret = (function imClosuring() {
  var secret = "I ate all the cake";
  return function () {
    return secret.split('').reverse().join('');
  };
}());

So in most programming languages, we can declare functions and variables to be what's called private. When we do so, they cannot be accessed by any script or user that's not in the same scope. In JS we don't have this luxury, so we have to be creative and create what's called a closure, or IIFE (Immediately Invoked Function Expression) which is just a fancy name for a function which we invoke immediately. See above how we added a function that's called imClosuring? I wrapped it in another set of parentheses to make it more readable, but you can see how I'm invoking it immediately after I'm done declaring it. If you pay close attention, you can see how the return value of imClosuring is yet another function. That function has access to the internal variable of imClosuring (secret), but no one else can access it directly! Lastly, the function with the access to secret, is now stored in obfuscatedSecret and we can invoke it like this:

obfuscatedSecret(); // Yields the secret reversed!

Closures are a common practice in JS for when you want to hide something, and that's exactly how we're going to hide the intermediate function in our Weather App:

/*
  Filename: weather_app.html
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/weather_app.html
*/

<script>


(function () { // <-- We've wrapped almost everything in a function

  var apiUrl = 'http://api.openweathermap.org/data/2.5/weather';

  var apiKey = 'fa32f';

  function getWeather(locationObject, callback) {
    var url = apiUrl +
      '?appid=' + apiKey +
      '&units=imperial' +
      '&' + parameterize(locationObject);

    makeRequest(url, function (responseText) {
      response = JSON.parse(responseText);
      callback(response);
    });
  }

  function parameterize(object) {
    var params = [];
    for (var key in object) {
      params.push(key + '=' + encodeURIComponent(object[key]));
    }
    return params.join('&');
  }

  function makeRequest(url, callback) {
    var xhrObject = new XMLHttpRequest();
    xhrObject.onload = function () {
      callback(xhrObject.responseText);
    };
    xhrObject.open('GET', url, true);
    xhrObject.send();
  }

  function getUserCoordinates(callback) {
    navigator.geolocation.getCurrentPosition(function (position) {
      if (position.coords) {
        callback(position.coords);
      } else {
        console.error("Didn't get a valid coordinate, meow!");
      }
    });
  }

  function printWeather(weatherData) {
    var currentTemperature = weatherData.main.temp;
    var location = weatherData.name;
    console.log("The current temprature in " + location +
                " is: " + currentTemperature);
  }

  function weatherForMyLocation() {
    getUserCoordinates(function (coord) {
      getWeather({
        lon: coord.longitude,
        lat: coord.latitude
      }, printWeather);
    });
  }

  function weatherForZipCodes(zips) {
    for (var i = 0; i < zips.length; i++) {
      getWeather({
        zip: zips[i]
      }, printWeather);
    }
  }

  window.weatherApp = { // <-- Pay attention here
    weatherForMyLocation: weatherForMyLocation,
    weatherForZipCodes: weatherForZipCodes
  };

})(); // <-- Note how we're immediately invoking here

</script>

But wait a second, now we need to expose our two interface functions somehow... Hmm. We do so by attaching those two functions to our window object. The window object in the browser is the top-most, global object that's available to all other code. In the browser, when you create anything in the global scope, it is attached to the window object automatically and becomes a property of it. So in our code, since we've created a sub-scope with our closure, which isn't part of the global scope, we need to attach to window explicitly like so:

window.weatherApp = {
  weatherForMyLocation: weatherForMyLocation,
  weatherForZipCodes: weatherForZipCodes
};

For convenience and readability we create a property on the window object called weatherApp and its purpose is just to group together our application's functions. Otherwise they'll just float in the void of the window object which is terribly cluttered already.

So now when you run our code (copy/paste, if you will, then just refresh the page) you will not be able to access any of our "private" functions. The only two functions available to you from the Console will be the ones we exposed. Only difference is that now they will be accessible like this:

weatherApp.weatherForMyLocation();
weatherApp.weatherForZipCodes([11211]);

Note how we don't need to explicitly precede with a reference to window when invoking above as it is implied. We could, however, invoke our functions like this as well:

// Referencing `window` explicitly:
window.weatherApp.weatherForMyLocation();

So wait a second, how the hell does our application work if we hide all of the intermediate functions? They are still very much necessary to our application logic. Well, since our two interface functions were originally declared within the same scope of the other functions, they can still access them. We can't, but they can! That's a closure.

Table of Contents
Home