Chapter 10 - Conditionals

Back to our application. The fourth point was:

  1. Declare a function that is responsible for getting the user's coordinate
function getUserCoordinates(callback) {
  navigator.geolocation.getCurrentPosition(function (position) {
    callback(position.coords);
  });
}

This is, once again, a function. This time it gets our user's coordinates using the browser's API. How does it do it? It queries the browser for it. How does the browser know the user's location? What do you care? It just knows it.

Well if you really wanna know how the browser knows your location, look it up. It's a combination of GPS (if your computer has one [your phone most likely does]), WiFi networks around you and their reception strength, or a simple IP address lookup (though the least accurate option). Either way, we're not here to reinvent the browser. We are here to use it. Abstraction. BAM.

Back to our function. Getting the user's coordinates. First line of our function, as you can see, calls the navigator, which is a helper object which contains some info on the browser's state and exposes some of its under-the-hood functionality.

Note that older browsers don't have that nifty navigator object, so be diligent enough with your users and add appropriate checks before shipping this code into the wild, meaning, don't assume navigator exists in all of your users' browsers -- check for its existence before assuming. We're about to learn how in a minute.

The navigator object has a property called geolocation. You can see what it has by typing navigator.geolocation in your Console, hitting Enter, clicking on the returned value and then inspecting it in the panel on the right. Or you can just use console.dir:

console.dir(navigator.geolocation);

geolocation as it turns out is just another object. I assume the reason behind nesting it like this was to group together methods related to geolocation under one object.

So geolocation as you may have already understood from our code above, has a method called getCurrentPosition, and we're going to invoke it like so:

navigator.geolocation.getCurrentPosition();

Again, how did I know this? Well I knew it was possible from reading stuff about it a couple of times and seeing prompts asking for me to allow site X to access my location across the web. So I knew it was possible, and when I wanted to implement it I just googled it and found docs online on how to use the browser's geolocation services.

Since there's a lot that needs to happen behind the scenes in order to get the user's position (the browser needs to first ask for the user's permission, then it needs to see what geolocation tools are available on the computer, then load them, etc.), this operation is also done asynchronously (because it involves IO). This simply means the browser is not going to sit around waiting for the coordinates to arrive. It will instead continue executing the rest of your code and serve its master user. Only when it has results from the operation (could be a failure, too), will it execute the code relevant to our geolocation logic via the callback we will provide to it.

See below how we're passing another function to be used as a callback to getCurrentPosition:

navigator.geolocation.getCurrentPosition(function (position) {

  // some code that depends on the location goes here

}); // <-- note how we still need to close our getCurrentPosition's
    // invocation parentheses even when it spans over multiple lines.

Our callback function takes one argument by the name of position. That is because when I looked at the docs for the getCurrentPosition method, I saw that it takes several arguments, with the first one being a callback for the "success" scenario. That callback, when called, is said to receive one argument -- a position object when it's done being fetched. Pause and think about this callback function for a moment.

So in other words: When getCurrentPosition is done and it's got a result for us, it will invoke our callback and pass the position to it.

And what do we do with this position (which, duh, is another object)? We just pass it to our original callback which we've been carrying around since the invocation of getUserCoordinates.

So when all of this is done, the callback we passed to getCurrentPosition invokes the callback we provided getUserCoordinates with (it still has access to it) with the needed coordinate as its argument. Note how we're extracting the coordinates (in the object position.coord) from the position object. We're doing this since our callback expects a coordinate object and not the full position object which could be an error and not contain the data we need at all.

In the event of the position object not being what we expected and not include the coordinates we need, then we need to do something else, but either way we should not invoke our callback because our callback is only useful when we have the coordinates it needs so badly.

So let's revise our line just slightly and add the following code to our function:

function getUserCoordinates(callback) {
  navigator.geolocation.getCurrentPosition(function (position){
    if (position.coords) { // <-- Added this
      callback(position.coords);
    }
  });
}

What is this if thing? Well, you should know by now how to inspect your code, so how about you play in your Console for a minute. Try using this if magic and see what it yields. I have a feeling you can start getting a good idea. Some magical things you could try:

if (false) {
  console.log(false);
}

if (true) {
  console.log(true);
}

Hmm I wonder which one is going to do what. Is it going to print false or true to the Console? Don't just stand there! Try and see for yourself! It's not like a mistake in your browser will launch a nuke and start a war, so what are you afraid of?!


So conditionals.

Conditionals are a huge part of programming. They basically mean if something is true, do one thing, otherwise (if it is false) do another thing.

What thing?

Consider the example we had before. If position.coords is true, the interpreter will perform what's inside the code block that follows (enclosed by the curly braces).

Now you probably think this is weird since we just said position is an object and coords is another object nested within it. So how can it be true or false to satisfy a boolean condition (only true or false)?

Well, it's not exactly about true or false, but rather about truthiness and falsiness. Those sound like complete and utter bullshit, but all it means is that some values (in JS, at least) are considered to be true and others are considered false when they are forced to behave in a boolean way. Generally speaking, these are considered falsy when they are forced to choose one or the other:

Falsy values:

Anything else in JS that's not listed above, meaning the boolean true, non-empty strings, non-zero numbers, objects and functions are considered to be truthy.

So in our previous example, when we said if (position.coords), we were trying to see if our coords object had a value that is not falsy. Meaning, we were trying to see if we have something of value in it and if so, perform the code in the block below it.

If the value in position.coords was falsy (most likely undefined or null in this case), then the code in the block below it would never get executed.

So what do we want to do in case we don't have a valid coordinate? Well we probably want to show an error in the Console to let the developer know that something went wrong.

We can do it like so:

if (position.coords) {
  callback(position.coords);
} else {
  console.error("Didn't get a valid coordinate, Meow!");
}

See how we added an else statement? This else statement follows the if code block immediately and tells the interpreter to perform the else code block if the condition in the if statement evaluated to false.

Get it?

This isn't too complex. If something is true then do one thing, otherwise do another thing. This is similar to human talk:

if (charlie.isCrying) { // He always does ...
  if (charlie.isHungry) {
    charlie.provide('food');
  } else if (charlie.isThirsty) { // pay attention here
    charlie.provide('water');
  } else {
    charlie.provide('love');
  }
}

This is not real code (we don't have the above properties defined on the charlie object we used earlier), so don't try to run it, but this is a good representation of human language in, theoretically, valid code. Even if you have no idea what the hell JS is, just by reading this you can infer what it means. Or should be somewhat able to.

If Charlie (he's a dog in case you've forgotten) is crying then you should check if he's hungry, if he is hungry, then give him food, else (if he isn't hungry), check if he's thirsty and if so give him water, if he's not hungry nor thirsty (but still crying, since we're still within the if (charlie.isCrying) block), then you just assume he just needs some love, so you default to giving him love.

So in the above example we've introduced two new concepts. One is the ability to nest conditionals (there's an outer conditional statement and there's an inner one), and the second new concept is following an else statement with another if statement as we've seen in those else ifs.

Nesting Conditionals

In the above example the outer conditional is if (charlie.isCrying). If this evaluates to false (he's not crying), then the interpreter won't even look at the block following it, meaning it won't even check to see if he's hungry or thirsty and won't provide him with anything.

The inner conditional statement is broken into three pieces, and gets evaluated when the outer one is true. In the case Charlie is crying, then we will check if he's hungry or thirsty and react accordingly.

So now let's talk about that else if:

if (charlie.isHungry) {
  // ...
} else if (charlie.isThirsty) {
  // ...
}

The above code means that if Charlie is not hungry, then the interpreter will move on to the else statement and evaluate it. This particular else statement is followed by another if statement which means before jumping into the code block underneath it, it will make sure Charlie is thirsty first. If he is not thirsty, then the block underneath won't get evaluated at all. Try it with other expressions in your Console.

What if Charlie can be hungry and thirsty, though? Then you'd drop the else statement before the second if statement since it isn't dependent on the result of the previous if statement at all, and it should look like this:

if (charlie.isHungry) {
  // ...
}

if (charlie.isThirsty) {
  // ...
}

So now we have two separate if statements that are mutually exclusive and have nothing to do with one another. Regardless of what the first condition yields, the interpreter will still evaluate the second one as well.

Table of Contents
Home