Frontend JavaScript Lab

In this lab, you’ll be introduced to jQuery, a popular library that helps speed up JavaScript web development to build interactive web pages.

Overview

The goal of this lab is to create a web app that will let us play the game “Galumphing Banderwoozles.” I didn’t come up with the name: the Spring 2014 15-251 TA’s did.

Nonetheless, the rules of the game are as follows:

  • You are given a board composed of 15251 by 15251 tiles.
  • Any tile can be one of three colors: red, green, or blue.
  • Initially, all tiles are blue, except for the tile in the top left corner, which is green.
  • The object is to turn the entire board red.
  • Green tiles can be clicked to turn them red. Once a tile is red, it stays red.
  • Clicking a green tile also affects it’s neighbors to top, right, bottom, and left.
    • When a green tile is clicked, all neighbors that are currently blue become green, and all those that are green become blue.
    • Recall that the red tiles stay red.

These rules are pretty confusing. If you want to get a feel for how the game works, you can play a working demo of the game here. Simply enter a board size (say, 5 rows by 5 columns) and start clicking on the green tiles.

15251 by 15251 is pretty big, so for our purposes, we’re going to let the player decide how big to make the board. Going one step further, this tutorial in general requires you to recall the rules of this game very rarely. As long as you know what goal we’re trying to achieve (which will almost always be stated), the only struggle should be figuring out the JavaScript required to implement it.

Diving in

The code and tutorial for this lab will be hosted on GitHub. Each step is a commit, allowing you to see what changed between steps. Links will be provided to the code of the step in question. Most of the explanations will not make sense if you do not also take a look at the corresponding code, so be sure to do that.

Step 1: Initial HTML and CSS

download starter code, diff for this step, code at this step

The first step takes care of getting the starter code. You can download it using this link. The starter code should contain two files when unzipped: a file called index.html and one called styles.css.

For more information about how to create things like this, check out the Web Dev Weeks talk on HTML & CSS.

Skimming over this step’s code, you can see that we’ve included our styles.css file containing all of our CSS, as well as an external file called normalize.min.css which will also help make things appear correctly.

You can also see a rough sketch of how the app will look: we’ve got a couple of text inputs to get the board size, we’ve got a couple of counters to keep track of some game stats, and we’ve got the tile-wrapper, which will hold the code for all of our tiles.

If you’re interested in this, you should definitely check out the HTML & CSS workshop, which shows you how to come up with the styles and content of a web page.

Step 2: Load the external scripts

diff for this step, code at this step

Now that we have all the HTML & CSS in place, we need to tell the browser where to look to load the JavaScript files that we’ll be using. This is done with the <script> tag.

For this project, we’ll be using two different JavaScript sources. First, we’ll load the jQuery, a JavaScript library that helps out tremendously with adding event listeners, manipulating the DOM, and doing other routine tasks. jQuery is a library written and hosted by someone else: this means we need to go grab an external link to it. To find it, search the Web for “Google jQuery CDN”. (A CDN, or content delivery network, is basically a super-fast network designed for hosting commonly-requested files.) On the first result page, you should see a large list of Google hosted libraries:

  1. Find the one titled jQuery,
  2. Copy the <script> tag under it, and
  3. Paste it into the file index.html.

Now that we’ve loaded our helper JavaScript library, we need to load the file which will contain our core application code. We’ll be calling this file main.js, so add a <script> tag that looks similar to the one you just copied, but with a source of src="main.js" instead:

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

With these lines in place in index.html, we should be good to go! Now we can start writing the core JavaScript code for our game.

Step 3: Add starter code in main.js

diff for this step, code at this step

We’re finally at the point where we can add some code to our main.js file!

With the first few lines, we get the opportunity to see one of the many quirks about JavaScript: something that’s not really necessary but that makes life a little bit easier.

We’re going to add the following lines to our JavaScript file:

;(function() {

})();

If none of this makes sense, just copy the code into main.js and move on. It’s not worth stressing over for this lab.

What these lines do is terminate any previous, dangling JavaScript statement (with the first semi-colon), create an anonymous function (with the function keyword), and then immediately call it (with the () towards the end). This ensures that we have our own execution context for all the code that we write. You can read more about it here, but you’re almost always going to want to follow this convention when writing JavaScript files.

Step 4: Listen for ready event

diff for this step, code at this step

If you’re familiar with a language like C/C++ or Java, you’ll know that every application has an entry point that’s usually called main. In Python there’s a similar concept using globally defined variables like __name__ == '__main__'.

JavaScript has no such entry point. When JavaScript is run in a browser, each line is run as it is encountered. While this is useful sometimes, a lot of the time what we actually want is for our JavaScript to be run after the page has finished loading; i.e., when it’s “ready” for us.

To make this happen, we use event listeners to run a particular function once a corresponding event occurs. Events are generated for many different actions and conditions within the browser; one of these is the ready event, which is fired on the document object whenever the page is, well, ready for us.

We can attach a handler, or a specialized function, to this event as follows. It effectively allows us to run a piece of code whenever the event fires. When writing event handlers, people tend to use anonymous functions:

$(document).ready(function() {
  // empty
});

Attaching a function to an event and having it be run at a later point is an incredibly common and unusually powerful paradigm in JavaScript. We call functions used like this callbacks.

What this does is attach an empty function to the ready event of the document element. Note the funny $(document) syntax at the beginning. This actually calls the function named $ with an argument of document, a global variable representing the page. $ is a function defined by jQuery that lets us do tons of things, including attach event handlers like we do with the .ready() method in this example.

Step 5: Add play function

diff for this step, code at this step

Now that the “ready” event listener is in place, we can add some boilerplate functions where we’ll write our core game logic.

Make sure you’re also following along in the diff for this step!

First, we’ll need a function called play. Right now, it’ll remain empty. This function will also be an event handler, so it’ll take a single argument representing the event that triggered the function to be called.

You may notice that the anonymous function we registered on the ready event from the last step was also an event handler. A logical question to ask might be, “Why didn’t our ready handler have to take an argument e?” The answer is that we could have declared our handler like this, but the body of that handler isn’t going to need to use it, so we just leave it out. JavaScript won’t complain if the number of arguments a function is called with and how many it’s declared with don’t match up. For more information, see here.

Now, with our empty play function in place, we’re going to add it as an event handler on the click event of the submit button. This button has an id of submit (check the HTML!), so we can use jQuery to select that element and bind the click event on that element to our play function.

Here we see our second use of $, the jQuery function. You should be thinking of this function as “selecting” the element specified by its argument.

Step 6: Get the user’s input for rows and columns

diff for this step, code at this step

So now we have a function that will be executed every time the user clicks on our submit button. Let’s start filling it in with something useful.

We said one of the things we’d have to do in order to set up the game state was to let the user choose how many rows and columns there should be. We can do just this using the two text inputs already present in the page.

By the time the user clicks on our submit button and fires off the play function, the text inputs with id’s of rows and cols will be holding values corresponding to the number of rows and columns that our board should have. We can use jQuery to extract these values, using the .val() function. We’ll go ahead and store these values in global variables.

Question: what should happen if the user clicks “submit” twice? Should we start a fresh game? Add or remove rows from the game in progress? There are many ways we could handle this situation.

If you tried this but clicking “Play!” doesn’t do anything, try reading the next step :P

For sake of simplicity, we’re just going to remove the submit button and both text inputs entirely. After selecting the form with id args that wraps the text inputs and submit button, we can use the .detach() method to remove them from the DOM.

Step 7: Prevent form submission event from bubbling up

diff for this step, code at this step

If you're unfamiliar with the terminal, you can ignore the Python command here, but you'll have to go back and make some changes to index.html:
  1. Prefix the "//cdnjs..." URL in index.html with "http": "http://cdnjs..."
  2. Do the same with the jQuery link in index.html
Then, open index.html using Chrome.

Once make the changes introduced in the previous step, go ahead and see if clicking the button removes the #args form. The best way to preview your work is to start a simple Python webserver running in your project’s directory with

$ python -m SimpleHTTPServer

then open http://localhost:8000 in your browser. If you need help with this, flag down a mentor! Otherwise, just open the index.html file in your browser.

What you’ll probably notice is that the form didn’t disappear, and it looked like the website reloaded the page. If you look carefully, you’ll probably also see that the URL changed to something with variable names, question marks, and ampersands. This happened because of something called “bubbling” which is another JavaScript feature which can seem like a quirk at times.

Whenever there is a button inside a form and the button is clicked, the browser thinks that the values of the inputs within that form need to be submitted. If you don’t specify where to submit this information on the <form> tag, then it assumes that you want it to be submitted to the current URL, effectively reloading the page.

This is pretty annoying, because we’d actually like to run our own code instead of having the browser do something for us. To turn this feature off, we have to understand what’s actually going on. Nearly every action a user has with a page generates some sort of event. Even though an event might have a particular element from which it originated, the event it self propagates up through the DOM tree, from that element to it’s parent element, all the way until it reaches the <html> tag. This is what’s known as “bubbling”, because the event bubbles up the DOM.

Our particular event handler is listening on the button itself, whereas the form submission event is attached to the form element; we want to shut off the bubbling before it can get from the button to the form. To do this, we can use the argument e, which gives us information about the current event. We can stop the “default” action (i.e., the event bubbling upwards) from occurring by calling e.preventDefault().

Try adding this line and see what happens. You should see that the #args form detaches and stays detached. If not, flag down a mentor!

By now, you’re probably starting to understand that there are a lot of idiosyncrasies in JavaScript whose solutions are not obvious. In cases like these, your best debugging tool is the ability to carefully describe what problem you’re having and searching for it on Google. At least when you’re starting out, it’s likely the problems you will experience will have relatively simple, though non-intuitive, solutions. Being able to Google effectively is invaluable in times like these.

Step 8: Initialize the game’s starting state

diff for this step, code at this step

Now that we can accept the user’s input with our form, we can actually begin setting up the initial board configuration. Specifically, the user has told us how many rows and columns to use, so we can use a nested for loop to set up the grid with the right dimensions.

Throughout this step, it’s a good idea to have a picture of how this step is supposed to work. Try drawing it yourself based on this description, or ask a mentor for clarification!

Since HTML elements are display and drawn across and then down, we’re going to draw one row at a time (as opposed to one column at a time). To make things easier, we’re going to wrap each row in a div with the class tile-row so that we can access arbitrary rows.

To create a new DOM element (basically, an element in the page), we use the jQuery function ($) with a string argument representing the HTML that we should use to construct that object. We just need to create a <div> with a class of tile-row, so the string we want is '<div class="tile-row"></div>'. We can then store this in a variable named $curTileRow (note that $ is a perfectly valid character to use in a JavaScript variable name). Finally, we use the jQuery append method to append this tile row to the tile wrapper.

We’ll be using a multidimensional array to maintain what color each board piece is. JavaScript arrays aren’t fixed in length (because they’re actually just special objects), so we’ll be dynamically adding a row every time we need a new one. What that means for us is that within the outer loop, where code will be executed once per row, we should add a row to our multidimentional array. This is done with the code boardColors[curRow] = [];, which sets the contents of the curRow’th row to an empty array.

Now that we’ve done the required setup for each row, we can work on adding the individual tiles that will ultimately constitute the columns. We’ll add a for loop inside our outer for loop that ranges over the entire number of columns.

The first thing this inner loop should do is create the DOM element for the tile residing in that column. This is done with the jQuery append method that we used before to add a row to the outer, wrapper div. Next, since all but the top left tiles should start out blue, we’ll set the color of this tile to blue (and worry about the one green one later). I’ll use a helper function called setColor to set the color of a tile. It’ll take a row, column, and a string representing a color. We’ll write this function after the next step. For now, just know that it takes care of setting the color for us.

Finally, we’ll have to set the click event on the div to handle what should happen when the user “makes a move,” or attempts to change the color of a tile. This part is actually very sophisticated! We’re going to be using a technique called closures, which is a very powerful concept that is used quite frequently in JavaScript.

The basic paradigm stems from the fact that JavaScript maintains a context of all the variables and their values for every function that is created. That means that we can create special contexts that bind additional variables that a function would find useful to use.

First you’ll note the helper function getTile. We’ll write this helper function in after next step as well, for but for now just now that it’s basically a wrapper around the $ function that we’ve been using this whole time. What that means is that it returns to us the same jQuery DOM object that we would normally get back, so we can set the .click attribute of that result to bind a function to the click event of that particular DOM element.

Next, we have an anonymous function wrapped in parentheses, (function(r, c) {…}), followed by (curRow, curCol). When you see this, a function definition wrapped in parentheses immediately followed by parentheses, this will create an anonymous function and immediately call it with the arguments specified in the second set of parentheses. This is called a self executing function, and you can read more about it here.

Remember that the argument to the .click function needs to be a function which takes one variable: the event object e that triggered the click event. We’re actually going to use this self-executing function to return a different function. What’s special about this returned function is that it will have access to the parameters of the outer function, namely the current row and current column. We can then reference those inside our returned function; in our case, we’re going to pass them on to move, a third helper function that will take care of the brunt of the game logic for us. We’ll start writing that function in three steps.

Since the function we pass to click must take an event object as an argument, we’ll make the function we return take just that argument, and we’ll pass it on to move just in case we need it.

Somehow this step ended up being much, much longer than I wanted it to be. It’s much easier to explain in person, so if it doesn’t make sense as stated, be sure to flag someone down.

Phew, that was a long step, but that’s it for the processing we need to do within the loop! If you remember from before, I said that we’d take care of changing the upper left tile to green later, so let’s go ahead and do that now. Remember that we use a helper function for this before, so we’ll use the same function again now.

Step 9: Set the initial values on the “scoreboard”

diff for this step, code at this step

One last thing to take care of before we start going nuts with our game is to set up number of greens, blues, and total tiles, and then display these values.

We’ll initialize a few global variables called reds, greens, and blues to 0, set greens to 1 (for the upper left tile), and set blues to the remaining number of tiles. Obviously, the total number of tiles is rows * columns, and this will be important because we will know the user has won if the number of reds is equal to this value.

Finally, we’ll use a couple more jQuery calls to display these values. The .html function on a jQuery object will set the inner HTML of that attribute to the text specified (if you pass something that’s not a string, it will try and cast it to a string). That means we can use this method, passing it blues and greens respectively, to set these colors to the right values. Remember that the HTML elements with id’s blues and greens house the data we want to display. Note that we don’t have to worry about setting the number of reds because it should already be set to 0 in the HTML (you can verify this).

Step 10: Define a couple useful helper functions

diff for this step, code at this step

As I mentioned two steps ago, we’re going to now define the helper function we used to both set a tile’s color given its indices, and to get the DOM node of an element given its indices.

The first of these, getTile, is a one-liner, though it is a bit complicated. We’ll be using the jQuery/CSS :nth() pseudo-selector, which basically allows us to access the nth element of a particular class. We’ll also be using the > selector, which selects the children of a particular node. Remember that we wrapped every row in a div with the class tile-row, so to get the nth row we can use `.tile-row:nth(‘

  • row + ‘). Then we want to access an individual tile (an element with the class tile), which is a child of a tile-row. That means the next selector needs to be a >. Finally, we want the nth tile, so in a similar fashion to before, we want to use .tile:nth(‘ + col + ‘). If you hadn't already figured it out, the :nth()` pseudo-selector takes as an argument an integer which represents the index of which element it should select.

Next up is setColor. We have two things to do: update our internal representation of the colors of the tiles, and then actually change the color of the element. Remember that we’ve been storing our internal representation of the board’s colors in a multidimensional array called boardColors. We want to access the element at [row][col] and set it’s contents to color. We’ll use this stored value later in processing what to do when the user clicks on a tile.

The second thing to do is change the actual color of the tile. In the CSS stylesheet (styles.css), there are a bunch of handy classes that will turn our element a particular color depending on which class is applied. First, we want to get the DOM element represented by the row and column we are dealing with, which is simple enough because we have a helper function to deal with that! We’ll next use the .removeClass jQuery method to remove any previous color class that had previously been attached to this element, and then the .addClass method to add the class corresponding to the color we want to make this tile.

There are a couple of things to note here. The first is that if removeClass doesn’t find the class you specified to remove, it doesn’t do anything. The second is that you can chain jQuery calls. Note that there are no semicolons after each line, only the last line. Every time we call a jQuery method that’s operating on a DOM element like this, we can chain as many calls as we want on top of each other to keep modifying the current jQuery DOM element.

Step 11: Start writing move function: get adjacent tiles

diff for this step, code at this step

We’re approaching the home stretch! move is the last function we’ll need to implement.

Recall that move will be called every time someone clicks a tile element, and it will have access to the current row and column because of the closure we used when initializing the click event. We’ll use this method to handle processing all the rules of the game that we outlined in the Overview.

In the rules, we specified that the only tiles we can actually click on are the green ones, so let’s check if the current tile is green.

Remember that we have access to the row and column of the clicked tile from the parameters to the move function, and we have a multidimensional array which will give us the color of a tile given a row and a column. Once we have this stored in a variable, we can check to see whether that color is 'green'.

If it is green, we need to change it’s color to red, increment the number of reds, and decrement the number of greens.

Step 12: Get and check whether the neighbors are valid

diff for this step, code at this step

Once we’ve toggled the current tile to red, we need to process the current tile’s neighbors. If we know what the current row and column are, we can easily get the row and column of the neighbors by adding or subtracting one to the appropriate variable.

Note that some of these cells will actually not be valid; for example, if we are in the 0th row, trying to access the 0 - 1 = -1th row will be invalid. We can check whether a variable is valid by checking the appropriate condition… beware of off by one errors!

To make things easier, we’re going to utilize the fact that JavaScript variables are dynamically typed. Since we don’t have to declare a variable’s type, we could assign either a jQuery DOM element or null to a variable and let the JavaScript interpreter determine this at runtime. Combining this with the JavaScript ternary operator, <condition> ? <value if true> : <value if false>, we can very conveniently check the edge conditions, get the neighbor if safe, and evaluate to null if not.

Once we’ve done this, I’m going to stick all four variables into an array with their corresponding coordinates so that we can conveniently iterate over the array, and do the same processing for all four.

Step 13: Use Array.forEach to iterate over each neighbor

diff for this step, code at this step

This time, when iterating over each potential neighbor, we’re going to use a different style of JavaScript for loop called Array.forEach. This is a function present on all JavaScript arrays that allows you to loop over all the elements of a list without using indices.

The way this works is you specify a function to the .forEach() method. This function takes as parameters the current element of the list, the index of the current element, and the a reference to the array the function was called on (in our example this isn’t necessary because we have access to the original array variable, but there are times when this isn’t the case). Since this function is a callback, it will be evaluated once for each element of the array, and the appropriate values will be substituted in.

Step 14: Process each neighbor within Array.forEach

diff for this step, code at this step

Within the forEach callback, we want to check to make sure that the current neighbor we’re processing exists, i.e. is not null. Since the actual element we’re iterating over is an object containing the properties tile, row, and col, we need to check the $neighbor.tile property to see if the element itself exists. Since JavaScript values are all truthy, an object is only ever falsy if it’s null, we can simply use if($neighbor.tile) to check whether the neighbor exists or not.

Now that we know whether the neighbor exists, we can do the appropriate processing. Particularly, what we need to do depends on the color of the current element. Let’s get the color of the current neighbor using the same method we did for the clicked tile (making care to draw the row and column from the properties we set on the current $neighbor variable).

Now we can case on the color of the current element. If it’s green, we’ll need to toggle it’s color to blue and adjust the number of blues and greens, and vice versa it the neighbor is blue.

Step 15: Update scoreboard and check for winning conditions

diff for this step, code at this step

The only thing left to do is update the scoreboard and check whether the player has won the game.

If you recall the code to update the scoreboard from early, we will use the same code now, but add an additional line to update reds (because reds will have definitely changed this time).

If the number of reds is equal to the total number of tiles, that means that every tile has been flipped, so the user has won. If the number of greens is 0, then the user lost, because there aren’t any tiles that can be flipped. There’s a JavaScript function alert that lets you display a message in a dialog box, so we can use this to report a win or a loss.

It turns out that some boards are winnable and some boards aren’t (if you’re interested you should try to prove it). Regardless of this, I thought it’d be funny when I made this game and distributed it to my fellow 251 classmates to tell them that a non-winnable board was in fact winnable… it had some interesting consequences. Feel free to change the winning and losing messages to something more “agreeable.”

The final thing we’ll take care of is reload the page for if the user want to play again. We can do with with location.reload(), which will take the current page’s URL and reload that addresss.

That’s it! You should be able to try everything out and have it work in the browser. If you’re still stuck, or you’ve got errors, flag down a mentor or ask someone for help and we’d love to give you some hints to fix everything up.

Thanks!


I take great pride in writing good code, documentation, and tutorials. If you spot something that’s not quite right or you spot something confusing, let me know! Open an issue on https://github.com/ScottyLabs/wdw.

Written by Jake Zimmerman, July 2014