Skip to content

Instantly share code, notes, and snippets.

@jeryj
Last active December 2, 2016 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeryj/5dbdeb7eca8fb10ea94b69fc2a309530 to your computer and use it in GitHub Desktop.
Save jeryj/5dbdeb7eca8fb10ea94b69fc2a309530 to your computer and use it in GitHub Desktop.
Bloc Frontend Mentor Assessment

Javascript Fundamentals Answer

Hey!

I don't know how many times I've been up late at night stuck on a problem. That may be step one in becoming a front-end developer :)

You're definitely on the right track here. Your prizes array (the ['A Unicorn!', 'A Hug!', 'Fresh Laundry!'] part) is set-up correctly and you have the structure of the "for" loop right. That's no small feat, so feel good about that one!

Before we get into this, know that this is a problem that every front-end developer has struggled with before. The answer lies in learning about Scope and Closure. It's a tricky little problem, and I'll explain it fully later in this email.

The most important thing for you to learn now is to know how to solve a problem. So, let's take this opportunity to learn a few troubleshooting techniques to help us figure out what's going on.

The first thing to do to troubleshoot this is to open the JavaScript console in your browser. In the Chrome browser, you can get to the console by opening the Developer Tools (go to View > Developer Tools > Developer Tools). A Developer Tools panel will open. Click the tab that says "Console".

The console is a basically a playground and log. We're going to use it as a log here so the code can tell us what is going on. For example, we might think prizes.length equals 3, but maybe it doesn't. We can do things like console.log(prizes.length); and it will tell us what prizes.length really is! (Hint: It really is 3 :)

I like to think of console.log() as a less annoying alert().

Now let's add some code to our JavaScript so we can log something! Add console.log('btnNum is '+btnNum); to your "for" loop to log the value of btnNum.

After you do that, your code should look something like this:

for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
    // Add this console.log line to log what btnNum we're on
    console.log('btnNum is '+btnNum);

    // for each of our buttons, when the user clicks it...
    document.getElementById('btn-' + btnNum).onclick = function() {
        // tell her what she's won!
        alert(prizes[btnNum]);
    };
}

Now reload your page and check out the console in the Developer Tools panel. If everything worked correctly, you should see this in your console:

btnNum is 0
btnNum is 1
btnNum is 2

Each time the for loop runs (also known as an iteration), it logs what the current value of btnNum is. Pretty handy!

We can see that it's successfully looping through all the buttons. So, what gives? Why isn't it working?

Well, when we click a button, it says the message is undefined. Let's find out what btnNum it thinks we're clicking by using another console.log().

Add console.log('You clicked button number '+btnNum); inside of the onclick function, right before the alert(prizes[btnNum]); line. By doing this, it will log whatever the value of btnNum is when we click a button. That will help us figure out what's going on.

Your for loop code should now look something like this:

for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
    // log what btnNum we're on
    console.log('btnNum is '+btnNum);
    // for each of our buttons, when the user clicks it...
    document.getElementById('btn-' + btnNum).onclick = function() {
        // log what button it thinks we're clicking
        console.log('You clicked button number '+btnNum);
        // tell her what she's won!
        alert(prizes[btnNum]);
    };
}

If everything went correctly here, you'll see that the console is logging You clicked button number 3. Wait... what?! There's no button number 3! There are only buttons 0, 1, and 2! Same thing with the prizes array. There are three items in the array if we count them normally, but, we're coding! So, we need to count them as JavaScript sees them (which starts at 0).

Let's lay it out together so we make sure we understand the prizes array values:

console.log(prizes[0]); // should log 'A Unicorn!'
console.log(prizes[1]); // should log 'A Hug!'
console.log(prizes[2]); // should log 'Fresh Laundry!'
console.log(prizes[3]); // what do you think this one will log?

Did you get it right? prizes[3] logs undefined. We're back to where we started, but now we have isolated the problem:

Clicking any button always tries to get prizes[3], which doesn't exist, so it alerts undefined.

Now we have to figure out how to fix it! To do that, we go back to the Scope and Closure thing I mentioned at the beginning.

Scope

Scope is how we talk about what variables in JavaScript equal in different parts of the code.

Say we have this code:

// a variable, named foo
var foo = 1;
// another variable, named bar
var bar = 2;

Pretty simple. We know what foo equals, and we know what bar equals. Let's complicate it a little.

var foo = 1;
var bar = 2;

function add() {
    return foo + bar; // returns 3;
}

So, even though our variables foo and bar are defined outside of our function add(), the add() function still knows about foo and bar!

A variable defined outside of any function is said to be in the Global Scope. So, because foo and bar were defined in the Global Scope, every function knows about foo and bar, for better or worse. (Hint: it's for worse! Scope can be a pain, as you're finding out right now :)

There's also a Local Scope. A locally scoped variable is only available in a limited part of the code, such as inside a function.

Here's an example of local scope.

function localAdd() {
    // var a and b are locally scoped.
    var a = 1;
    var b = 5;
    return a + b; // returns 6;
}

// now let's try to access one of the variables outside of the function
console.log(a); // logs 'undefined'

Because the variables a and b were defined in the localAdd() function, they are only available inside of that function. They are locally scoped, so they won't be available outside of their function (the global scope).

Functions can be written inside of other functions too. Any variables are accessible to the function they were written in, and any functions nested inside them. That sounds kind of confusing, so let's illustrate it:

var globalA = 1;

function localAdd() {
    var localB = 2;

    function innerLocalAdd() {
        var innerLocalC = 3;
        return innerLocalC + localB; // returns 5 because it has access to localB;
    }

    console.log(innerLocalC); // returns a `innerLocalC is not defined` error because it's outside the function it was defined in.
}

With this, we can see that local variables are available inside their function and any functions inside them. Variables can go inwards (functions inside of functions), but can't go outwards. Because of this, the variable innerLocalC doesn't exist outside of innerLocalAdd().

Apply what we learned about scope

Let's go back to our problem up above. Do you think the btnNum variable defined in our for loop is in the Global or Local scope? Let's find out!

Add console.log(btnNum); after the for loop has ended and see what it gives you.

Your code should look something like this:

for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
    // log what btnNum we're on
    console.log('btnNum is '+btnNum);
    // for each of our buttons, when the user clicks it...
    document.getElementById('btn-' + btnNum).onclick = function() {
        // log what button it thinks we're clicking
        console.log('You clicked button number '+btnNum);
        // tell her what she's won!
        alert(prizes[btnNum]);
    };
}
// Log what btnNum is after the for loop
console.log('After the loop, btnNum is '+btnNum);

Did you guess that btnNum was 3? Because it equals 3 instead of being undefined, btnNum is a globally scoped variable.

And because it's a global variable, anytime we click a button, it thinks btnNum should be 3 because that's the last value it was equal to in the for loop.

But, we don't want what btnNum is equal to at the end of the for loop, we want the value while it's still IN the loop. To do that, we need to let the global variable of btnNum be passed as a local variable so it doesn't get overwritten on each iteration of the for loop.

This is where Closures come in.

Closures

Closures are created when we return a function instead of just a normal variable (like a string or an integer/number). Not only does a closure return a function, but it returns the current state of that function. It's like a function with a memory. Any variables defined inside the closure are remembered for the next time you call the closure.

Here's an example to illustrate it:

function ordinaryFunction() {
    var a = 1;
    // we're returning an ordinary variable. Booooring.
    return a;
}

function closure(a) {
    // we're returning a function! Sweet!
    return function(b) {
        return a + b;
    }
}

var a = ordinaryFunction(); // returns 1;

var add2 = closure(2);
// add2 equals the returned function inside of closure(),
// and it *remembers* that we passed it the number 2 as the variable `a` value.
// So, add2 equals the function(b) {return a + b;} where `a` is equal to 2.
// Weird, right?

// with this, we're passing the number 5 to the inner function of the closure we created.
add2(5); // returns 7, because it's returning 2 + 5

So, for our purposes, a closure is a function that will remember the current state of all the variables when it is created. This should come in handy for our original problem :)

Applying Closures

To recap, our problem is:

  • our code uses the globally defined variable btnNum when we click a button.
  • Because btnNum is defined by the last iteration of the for loop, it equals 3.
  • We don't want it to equal 3. We need to save the value of btnNum each time it loops, instead of the globally defined btnNum.

Now that we know about closures, we can apply them to solve our problem! Because closures save a snapshot of the current state when they're created, we can use a closure to "save" the value of btnNum when the closure is created.

Easier said than done, right? (Actually, it wasn't even easy to say it!)

Let's give it a try! We know we need to create a function to use as a closure, and we know that a closure is made when a function returns a function. So, let's start with creating a closure function that returns our prizes alert.

function alertPrize(btnNum) {
    // creating our closure that will remember the
    // the `btnNum` value
    return function() {
        alert(prizes[btnNum]);
    }
}

The alertPrize() function receives the value of btnNum, then returns a function that alerts(prizes[btnNum]) of the original btnNum value, not the global btnNum value. We've created a closure!

Now we're on the home stretch. Let's fix the original problem by calling alertPrize(btnNum) in our for loop. (I'm going to remove the console.log lines in this example to make it easier to read).

var prizes = ['A Unicorn!', 'A Hug!', 'Fresh Laundry!'];

for (var btnNum = 0; btnNum < prizes.length; btnNum++) {
    // for each of our buttons, when the user clicks it...
    // this creates our closure,
    // which will save the `btnNum` value when it's created
    document.getElementById('btn-' + btnNum).onclick = alertPrize(btnNum);
}

function alertPrize(btnNum) {
    // creating our closure that will remember the
    // state of the passed `btnNum` value
    return function() {
        alert(prizes[btnNum]);
    }
}

That's it! By calling alertPrize(btnNum) inside the the for loop, we create a closure that remembers the value of btnNum at the time the closure is created. Now, when we click a button, it will remember that btnNum value and return the prize we wanted!

Awesome.

Closures are a doozy of a topic. I promise that most of JavaScript isn't as hard. Closures are a tricky thing for everyone to wrap their head around, and it's a really misunderstood topic. Don't be afraid to ask questions if you don't quite get it yet. That's what I'm here for!

Bonus question. How does prizes[btnNum] inside the alertPrize() function know about the prizes variable?

Best, Jerry

Angular Answer

Hi!

I've never actually used Angular, but I'm happy to learn alongside you and help out with any problems you encounter.

This article "AngularJS: Factory vs Service vs Provider" by Tyler McGinnis is focused on exactly what you need to know: https://tylermcginnis.com/angularjs-factory-vs-service-vs-provider-5f426cfe6b8c#.pqttljw9y

I'll read through it too. That way, if you have any questions about it, I'll be able to help out.

Sorry I couldn't give you a great answer right off the bat, but I promise we'll get this figured out!

Best, Jerry

Libraries vs Frameworks Answer

Great question! Let's use a building analogy to help figure this out.

Imagine you've been hired as a construction contractor to build a new hotel.

You could go to the hardware store, pick up a hammer and a saw, get some nails, and buy some wood, and start building. You can certainly do it, but... that might take awhile. This is the equivalent of using vanilla JavaScript. JavaScript has everything you need, but it can be slower, and difficult to give your code structure.

So, you've decided being a purist and building without any electric tools is great and all, but you don't have time for that! You go back to the hardware store and pick up some power tools to make the common things a lot faster and easier. You're going to make a lot of tricky beveled cuts, so you buy a compound miter saw and a table saw. You get nail gun and a drill to make building go faster. This is what a library like jQuery does for you. It doesn't make any decisions for you on how to build. It just gives you tools (functions) to make the common things easier and faster. Tools like the function addClass().

(Pro tip: Every other Stack Overflow comment is "jQuery IS JavasScript!" jQuery is, in fact, written in JavaScript. It's just a bunch of common functions for you to use to make your development go faster.)

Now you have the power tools so you can build faster. But... what are you going to build? How are you going to build it? You should start with the foundation, but, how deep do we need to make it? This would be a lot easier if someone would make these basic decisions for me and I could fill in the gaps...

Fortunately for you, prefabricated buildings exist. All the hard stuff like building the rafters is done for you. You buy a prefab building that has the foundation made, steel frame built, electrical and plumbing finished, and a roof over it. It's a big, plain building ready to be filled in. This is your JavaScript framework.

JavaScript front-end frameworks, like Angular and React, give you a consistent structure to work in as well as makes complex things like data binding easier. Also, because everything is so consistent, it's easier to work on with a team. Every thing has a place, and there's a place for every thing. You can even use jQuery inside of a front-end framework, because jQuery is just a bunch of helper functions.

Now that we know the basics, let's compare them a little more directly:

jQuery doesn't give you any structure like a framework. You can put code wherever you want with jQuery (or plain ol' JavaScript), and write it however you want. On a team or complex app, this is a bad thing. It's referred to as "spaghetti code" because it's like a plate of spaghetti. There's no structure, so it makes it hard to know where to put things.

Frameworks offer the structure lacking in JavaScript code. It defines a rigid way of doing things, and that rigidity makes a lot of difficult tasks easier to implement and understand because it's consistent.

This is a blessing and a curse. Because of its rigidity, if you decide you don't like the way something is implemented, you might not be able to change it without abandoning the whole framework.

There's a lot more to consider before jumping in to frameworks, and how to pick the right framework (if any) for your site. I'm happy to talk more about that, so let me know if you want me to dive into it!

Let me know if you have any more questions about this. I'm always happy to help!

Best, Jerry

CSS Answer

Howdy mentee!

You just encountered a classic head-scratcher in CSS. This is a great growing moment in your CSS skills, and something you'll encounter a lot as a front-end developer. Let's dive in and figure out what's going on!

So, you have three blocks that are being floated using float: left;. This makes each block line up side by side. All good so far! But, the background isn't stretching down far enough. This has to do with something called "collapsed elements" which I'll explain later. First, we need to make sure we understand the basics.

A normal block-level element (like a <div>) takes up space on your screen and doesn't let anything go to the right or left of it. "Floating" a block to the left tells the elements after it that they can come up and occupy the space on the right of it.

Imaging your roommate is laying on the couch, taking up all the space, and you want to sit down. They're a normal block element. You want to sit down, so you ask them to float to the left. They sit up, and move over to the left side of the couch, clearing up room so you can sit down. What a nice roommate! But you should really tell them not to put their feet on the couch :)

This still doesn't answer the original question: Why isn't the background stretching down around our floated elements? This is where collapsed elements come in.

Collapsed Elements

When a parent element (in our case the parent elements are "pricing-tiers" and "pricing") only contains floated elements, its height collapses to nothing. It's like it doesn't have any content at all.

We don't want to add unnecessary content just to get our background though. That's just clutter. And then we'd have to manually add content every time we float something. There's got to be a better way. Fortunately, there is!

Clearing Floats

CSS has a handy property called clear. When an element has the CSS clear: both; applied to it, it tells the browser "Don't let this element wrap around a float. Force it down beneath any floated elements."

clear can take a few different values, such as "left" and "right", but these days you pretty much only see "both" being used. That tells the element to clear both right and left floats.

Psuedo-elements

We still need an element after the floated "tier" elements in order to clear the floats and get our background to stretch down properly. There are a few methods to do it, but the best, modern way of doing it is to use the :after pseudo-element.

Every element has two pseudo-elements: "after" and "before". These are elements that don't appear in the HTML at all (hence the "pseudo" part), but we can access them with CSS.

Here's what a pseudo-element would look like if it existed in the HTML:

<div class="some-element">
    <!-- :before pseudo-element, accessed via CSS as .some-element:before {} -->

    Some content or more <divs> can go here, or not. Whatever you want!

    <!-- :after pseudo-element, accessed via CSS as .some-element:after {} -->
</div>

Like the HTML shows above, the "before" always goes immediately after the element starts, and "after" goes immediately before the element ends.

There are lots of great uses for pseudo-elements, and clearing floats is one of them. Let's apply our new knowledge!

Clearing Floats with a Pseudo-Element

So, we know that:

  • we need some content in our "pricing-tiers" element in order to get the background stretched down around the floated "tier" elements.
  • we don't want to clutter the HTML or have to manually add elements every time we want to fix this problem, so we're going to use a pseudo-element.
  • using clear: both forces an element down beneath preceding floated elements; i.e. it doesn't let the cleared element float up next to the floated element.

Now we just have to put it all together! Here's the CSS to clear the floats and not clutter the HTML:

.pricing-tiers:after {
    content: '';
    display: block;
    clear: both;
}

That's it. Pretty anticlimactic, right?

A few quick notes here:

  • The "content" property has to be there. Pseudo-elements require it before they'll show up. If there's no "content" attribute, it'll never appear.
  • The "display: block" property tells the pseudo-element to behave like a block-level element (such as a <div>) and take up all the space on the couch :)

Sorry for the lengthy build up for that short code snippet. It's important to me that you really understand what's going on, because understanding the core concepts is how you'll be able to solve any problem you encounter.

Let me know if you need any more help on this or anything else!

Best, Jerry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment