About: DOM (Document Object Model)

While the following section has less to do with programming and more to do with the browser, it is nearly impossible to talk about JS without covering a bit of the browser's API and the DOM (Document Object Model). Long story short, this section has nothing to do with programming if all you really care about is making iPhone apps. If you wanna be a web dev (yay), it is pretty important. Either way, I think the DOM is hella fun and it'll put some of this stuff in context for you.

So let's forget about the Console for a minute. We don't really expect a human, end user to open their browser's Console every time they want some weather information. Therefore, we shall turn our application into a real web app!

Our web app is going to consist of three different files:

  1. weather.js -- is going to be the heart of our application, and is basically the JS that we've worked on throughout this entire book. However, we're going to introduce a few modifications to it in order for it to play nicely with the rest of our web application.

  2. main.js -- will be responsible for tying our Weather App to the browser's elements that we're going to create in a separate HTML file.

  3. styles.css -- will include some styling rules written in CSS (Cascading Style Sheets) to make things look purrdy.

  4. index.html -- for all of our HTML. This file will include HTML for basic structure of elements on the page and also reference all of our JS and CSS files, therefore, this will be our entrance point to our app.

Note that all of the above files are located in the GitHub repository here.

index.html

Let's start with index.html since it's our entrance point:

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

<!DOCTYPE html>

<html>

<!-- Head section, not visible to the end user and is used
to reference our other files -->

<head>

  <!-- Note the order of the files. main.js is dependent
  on weather.js and therefore is included after -->

  <link href="styles.css" rel="stylesheet" type="text/css">

  <script src="weather.js"></script>

  <script src="main.js"></script>

</head>


<!-- Body section, visible to the end user. The elements
we're gonna work with are all here -->

<body>

  <div class="container">

    <h2 id="text">Ugh.</h2>

    <p>
      Current Temperature in
      <span id="location">Your Current Location</span>:
    </p>

    <div class="temp-container">
      <div id="border"></div>
      <div id="temp">Loading...</div>
    </div>


    <form id="form" action="#">
      <input type="text" id="zip" placeholder="Enter ZIP Code" />
    </form>

  </div>

</body>

</html>

The HTML above includes the following (in order of appearance):

  1. A generic DOCTYPE declaration telling the browser which version of HTML we're using.

  2. A <head> opening tag declaring the beginning of our head section which is invisible to the user and includes meta information for the browser to use.

  3. A <link> tag used to include our CSS file.

  4. Two <script> tags to include our JS files.

  5. The beginning of our <body> section which is visible to the end user.

  6. A <div> element which is used to group together other HTML elements. It also has a class attribute so we can reference it directly in our CSS or JS files.

  7. An <h2> element used to create a header. It has an id attribute which is also used to reference it later from our CSS or JS files. The only difference is that id, unlike class, has to be unique. So we can only use the "text" id (id="text") once in the HTML file.

  8. More divs which are used to group some elements together.

  9. A <form> element used to group input elements used to get user input.

  10. An <input> element with type of "text" which will be used for the user to enter her zipcode.

Play a bit with the different HTML elements so you can see what they look like IRL. Another tab in your DevTools which will be helpful for inspecting the HTML on the page is the "Inspector" ("Elements" in Chrome).

main.js

main.js is the script that ties it all together. Read the inline comments to understand more!

/*
  Filename: main.js
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/web/main.js
*/

/*
  This file is intended to tie our Weather App to the DOM
  elements. Since it relies on weather_app.js, we need to
  make sure to include this file AFTER we include
  weather_app.js
 */

(function () { // <-- Creating a closure like we did before


  /*
    Creating an array which includes multiple objects. Each
    object is used to set a minimum temperature and a short
    description to give the user
   */
  var descriptions = [
    {
      min: 80,
      text: "Ugh, it's hot as balls."
    },
    {
      min: 60,
      text: "It's warm, brah."
    },
    {
      min: 40,
      text: "Getting chilly, eh?"
    },
    {
      min: 20,
      text: "Maybe moving to LA is not such a bad idea after all."
    },
    {
      min: -Infinity,
      text: "Shit. Fuck. Shit."
    }
  ];

  /*
    Declaring variables that will be later accessed by our
    functions.  We declare them here so our functions can
    modify them and yet maintain their value between
    different function calls.
   */
  var tempEl, textEl, zipInputEl, locationEl, borderEl;

  /*
    This function is intended to run when our page has
    completed loading and all the necessary elements are
    present on the page.
   */
  function init() {

    /*
      Get reference to DOM elements we're going to use
      throughout the app. Again, these variables will be
      accessible in the rest of the functions as well,
      since they were declared outside of this function.
     */
    zipInputEl = document.getElementById('zip');
    tempEl     = document.getElementById('temp');
    locationEl = document.getElementById('location');
    textEl     = document.getElementById('text');
    borderEl   = document.getElementById('border');

     /*
       Attach an event handler to our form so when it is
       submitted our formHandler function below will be
       called.
      */
    document
      .getElementById('form')
      .addEventListener('submit', formHandler);

    /*
      Start with loading weather data for the user's current
      location and then update it on the page using the
      updateWeather function.
     */
    weatherApp.weatherForMyLocation(updateWeather);
  }

  /*
    Attaching our init function to be called only after the
    browser finished loading the page and fired the 'load'
    event (built-in).
   */
  window.addEventListener('load', init);

  /*
    This function is responsible for returning the
    (in-)appropriate description based on the temperature
    provided to it.
   */
  function getDescription(temp) {
    for (var i = 0; i < descriptions.length; i++) {
      if (temp >= descriptions[i].min) {
        return descriptions[i].text;
      }
    }
  }

  /*
    This function inserts the data in the right places every
    time we have new weather information
   */
  function updateWeather(weatherData) {
    var temp = weatherData.main.temp;
    locationEl.innerText = weatherData.name;
    tempEl.innerHTML = temp + " &#8457;";
    textEl.innerText = getDescription(temp);

    // Try to figure our what we're doing here:
    borderEl.style.transform =
      "rotate(" + (temp / 100) * 360 + "deg)";
  }

  /*
    This function just handles our form 'submit' event,
    makes sure it doesn't actually submit (would cause a
    page change), and then fires a temperature lookup based
    on the ZIP code the user entered
   */
  function formHandler(event) {
    event.preventDefault();
    var zip = zipInputEl.value;

    // invoking our Weather App with a zipcode!
    weatherApp.weatherForZipCodes([zip], updateWeather);
  }

})(); // <-- again, we have to immediately invoke our function

weather.js

Our original app as you already know it. Note how this time it does not include the awkward <script> tags we previously had since now we are dealing with a real JS file and not HTML anymore. The browser knows to parse the content or our file as JS.

/*
  Filename: weather.js
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/web/weather.js
*/


(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, callback);
  }

  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 () {
      var parsedResponse = JSON.parse(xhrObject.responseText);
      callback(parsedResponse);
    };
    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 weatherForMyLocation(callback) {
    getUserCoordinates(function (coord) {
      getWeather({
        lon: coord.longitude,
        lat: coord.latitude
      }, callback);
    });
  }

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

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

})();

styles.css

And last, our styles.css file with some rules to make it all look visually OK. CSS is a world on its own and I promise it will eventually make more sense as you explore more of web development. For now just keep in mind that words preceded by a dot (.) are used to reference classes in the HTML (i.e. <div class="container">), words preceded by a pound sign (#) reference ids (i.e. <div id="form">), and words that are not preceded by a pound sign or a dot (i.e. neither classes nor ids), reference tag names (i.e. <body>).

Commas separating between selectors (classes, ids, or tag names) -- for example, body, .someClass, \#someId, mean the CSS rule will apply to all the listed selectors separately. If only spaces, w/out commas, were to separate our selectors, then the CSS rule will only apply to the last element which is nested in under the selectors preceding it in the same order.

/*
  Filename: styles.css
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/web/styles.css
*/


html, body, .container {
  height: 100%;
  margin: 0;
}

body {
  font-family: "Helvetica Neue";
  font-size: 16px;
  color: white;
}

.container {
  overflow-y: auto;
  text-align: center;
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px;
  box-sizing: border-box;
  background-color: rebeccapurple;
}

.temp-container {
  position: relative;
  margin: 50px auto;
  width: 300px;
  height: 300px;
}

.temp-container #border {
  border: 20px solid rebeccapurple;
  border-bottom-color: white;
  border-radius: 100%;
  width: 100%;
  height: 100%;
  position: absolute;
  box-sizing: border-box;
  transition: transform 2s ease-out;
}

.temp-container #temp {
  top: 50%;
  position: relative;
}

#form {
  width: 300px;
  margin: 0 auto;
}

#form #zip {
  height: 30px;
  width: 100%;
  text-align: center;
  border: none;
}

Now I know this is a lot of new info. The above snippets of code cover HTML, DOM, the browser API, CSS and probably some more. We can't cover it all since this is going into specifics which this book wasn't meant to do, but at this point you have all the knowledge you need to understand the major points of the above application. If you want more of this stuff, go ahead and pick up a book on front-end development and the DOM. Make sure it's up-to-date as this business changes by the minute.

Again, all of this code is available on the GitHub repo here so you can see how it runs in your browser without having to copy the whole thing by hand (although you know how I feel about you copying code).

In order to run the above application so you can at least enjoy it, make sure you have all of your files in the same directory, and open the index.html file.

Once you have it up and running, I strongly encourage you to modify things, put debugger statements (or breakpoints) and console.log all over the place. This is how you'll learn the most.

From the CLI (Command Line Interface)

Alternatively, if our application was geared more towards computer geeks, or those who prefer their data w/out the fuss of the GUI (Graphical User Interface), we can make our application be a command-line application so we can run it from the Terminal. To do so we'll have to run JS not in the browser, and for that we're going to use Node.js.

Most computers (Macs at least) have Node built-in in the command-line. To access it, simply type node in your Terminal (to get Terminal running on Mac, look for it in your Spotlight search). If you got a JS REPL on the screen, then you have Node installed. If not, guess what? You don't, and you'll have to install it yourself (on Mac you can use Homebrew to do so easily, and on Linux you can probably use your built-in package manager, "apt-get" or "yum"). If you're on a Wee-Wee computer (Windows), it still can't be terribly bad -- check out this blog post.

With Node, our application is going to run a bit differently. First off, we need to create another file that we could run as an executable from the command-line and will be responsible for interacting with our weather.js file.

weather_cli.js

This is going to be the entrance point to our app for when we will run it from the command-line as shown below. Keep in mind the dollar sign ($) is just to notate a command-line prompt. You do not need to type it.

$ node weather_cli 11211 80634

And the file itself:

/*
  Filename: weather_cli.js
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/cli/weather_cli.js
*/


// Load our Weather App module from the weather_app.js file
var weatherApp = require('./weather');

/*
  Get the command line arguments which are stored in process.argv as
  an array. The 'slice' method on JS arrays returns the elements
  from the index provided to it. Since process.argv also contains the
  command (node) and the first argument (the filename), we only need
  the arguments following (the ZIP codes)
 */
var zipCodes = process.argv.slice(2);

/*
  Invoke our weatherForZipCodes function with the ZIP codes we were
  provided with and print them to the screen
 */
weatherApp.weatherForZipCodes(zipCodes, function (weatherData) {
  var temp = weatherData.main.temp;
  console.log(
    "Current Temperature for " + weatherData.name + ": " + temp
  );
});

weather.js

And our slightly modified weather.js which now will be able to work from both, the command-line with Node, and the browser:

/*
  Filename: weather.js
  Find me on GitHub: 
  github.com/ughcode/app/blob/master/chapter-14/cli/weather.js
*/

(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, callback);
  }

  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 () {
      var parsedResponse = JSON.parse(xhrObject.responseText);
      callback(parsedResponse);
    };
    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 weatherForMyLocation(callback) {
    getUserCoordinates(function (coord) {
      getWeather({
        lon: coord.longitude,
        lat: coord.latitude
      }, callback);
    });
  }

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

  // The only difference lies here:

  // check if we're in command line environment:
  if (typeof module !== 'undefined' && module.exports) {
    global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
    module.exports = {
      weatherForZipCodes: weatherForZipCodes
    };

  // else, if we're in a browser environment:
  } else if (typeof window !== 'undefined') {
    window.weatherApp = {
      weatherForMyLocation: weatherForMyLocation,
      weatherForZipCodes: weatherForZipCodes
    };
  }

})();

Notice how we're only exposing weatherForZipCodes when we're using Node (in a command-line environment) as the user location API is only available in the browser. Additionally, if our if statement indicated we're in a command-line environment, we initialize XMLHttpRequest with a custom Node module which we still need to install. Since XMLHttpRequest is originally only available in the browser, we'll have to install an external module with the same interface from here. To do so, in your Terminal when you're in the same directory of your application type:

# To see in what directory I'm currently in:
$ pwd # Yields: /Users/peleg/code/book/app

# To see what files are in this directory:
$ ls # Yields: weather.js weather_cli.js ...

# To install the module "xmlhttprequest":
$ npm install xmlhttprequest # will take a minute...

And to finally run this whole thing:

$ node weather_cli 90210 11211 80634

Outputs:

Current Temperature for Beverly Hills: 74.23
Current Temperature for Long Island City: 52.07
Current Temperature for Greeley: 64.27
Table of Contents
Home