Now that you've gotten your feet wet, let's wade a bit deeper into the JavaScript language. In this chapter, we'll go into more detail about the basic elements of JavaScript and introduce you to other aspects of the JavaScript language, such as loops, arrays, and more about functions (don't let your eyes glaze over; we promise that it'll be easy).
You'll see how you can use JavaScript to write your Web pages for you, learn how JavaScript handles errors that the user makes, and much more.
See it in action: Around and Around with Loops
It's common in programming to test for a particular condition and repeat the test as many times as needed. Let's use an example you probably know well: doing a search and replace in a word processor. You search for one bit of text, change it to a different text string, and then repeat the process for all of the instances of the first string in the document. Now imagine that you got a program to do it for you automatically. The program would execute a loop, which lets it repeat an action a specified number of times. In JavaScript, loops become a vital part of your scripting toolbox.
The kind of loop that we mostly use in this book is the for loop, named after the command that begins the loop. This sort of loop uses a counter, which is a variable that begins with one value (usually 0) and ends when a conditional test inside the loop is satisfied.
The command that starts the loop structure is immediately followed by parentheses. Inside the parentheses you'll usually find the counter definition and the way the counter is incremented (i.e., the way the counter's value is increased).
In the next several examples we're going to build a simple yet familiar application, a Bingo card. We'll use each example to show you a new aspect of JavaScript. We'll begin with an HTML page, Script 3.1. It contains the table that is the Bingo card's framework (Figure 3.1:). Take a look at the script, and you'll see that the first row contains the letters at the top of the card, and each subsequent row contains five table cells. Most cells contain just a non-breaking space (using the HTML entity ); however, the third row contains the Free space, so one table cell in that row contains the word "Free". Note that each cell has an id attribute, which the script uses to manipulate the cell contents. The id is in the form of square0, square1, square2, through square23, for reasons that we'll explain below. At the bottom of the page, there's a link that generates a new card.
Script 3.2 is the CSS file that we're using to style the contents of the Bingo card. If you don't know CSS, don't worry about it; we'll explain it in more detail in Chapter 11, and it doesn't matter much here anyway. The HTML and CSS pages won't change for the rest of the Bingo card examples, so we're only going to print them once here.
This example shows you how to set up and use a loop to populate the contents of the Bingo card with randomly generated numbers. Script 3.3 contains the JavaScript you need to make it happen. The card that is generated from this script is not a valid Bingo card, because there are constraints on which numbers can be in particular columns. Later examples add to the script until the resulting Bingo card is valid.
To use a loop to create the table's contents:
window.onload = newCard;
This is in Script 3.3. This line calls the newCard() function when the window finishes loading. It's common to use an event handler to call a function.
function newCard() {
This line begins the function.
for (var i=0; i<24; i++) {
This line begins the loop. Programmers traditionally use the variable i to denote a variable used as a counter inside a loop. First i is set to 0. A semicolon signals the end of that statement and allows us to put another statement on the same line. The next part is read as "if i is less than 24, do the following code inside the braces." The final bit (after the second semicolon) adds 1 to the value of i. Because this is new, let's break that down a bit. The i++ part uses the ++ operator you saw in Chapter 1 to increment the value of i by 1. The loop will repeat 24 times, and the code inside the loop will execute 24 times. On the first go-through, i will be 0, and on the last go-through i will be 23.
var newNum = Math.floor (Math.random() * 75) + 1;
Inside the loop, we create a new variable, newNum, and fill it with the result of the calculation on the right side of the parentheses. The built-in JavaScript command Math.random() gives us a number between 0 and 1, such as 0.123456789. Multiplying Math.random() by the maximum value (remember, values in Bingo cards can be from 1 to 75) gives us a result between 0 and one less than the max value. The floor of that result gives us the integer portion, i.e., an integer between 0 and (max value -1). Add one, and we have a number between 1 and our maximum value.
document.getElementById ("square" + i).innerHTML = newNum;
This is where we write into the table the value of the random number we just got. We get the element with the id named square with the current value of i concatenated onto it. For example, the first time through the loop, the value of i will be zero, so the line gets the element with the id of square0. Then the line sets the innerHTML property of the square0 object to the current value of newNum. Then, because we're still inside the loop, steps 4 and 5 happen again, until the whole Bingo card is filled out.
See it in action: Passing a Value to a Function
You'll often want to take some information and give it to a function to use. This is called passing the information to the function. For example, look at this function definition:
function playBall(batterup)
The variable batterup is a parameter of the function. When a function is called, data can be passed into the function. Then, when you're inside the function, that data is in the batterup variable. Functions can be passed just about any data you want to use, including text strings, numbers, or even other JavaScript objects. For example, the batterup variable could be passed a player's name as a text string ("Mantle"), or his number in the lineup (7) (although mixing the two can be a very bad idea until you really know what you're doing). Like all variables, you should give the ones you use as function parameters names that remind you what the variable is being used for.
You can have more than one parameter in a function. Just separate them inside the parentheses with commas, like this:
function currentScore(hometeam,visitors)
We would like to underscore that parameters are variables, and so they can be passed a variety of different values when the function is called. So these code fragments are all equivalent:
currentScore(6,4);
var homeScore = 6;
var visitingScore = 4;
currentScore(homeScore,visitingScore);
currentScore(6,3+1);
For all three examples, once we're inside currentScore(), the value of hometeam is 6, and the value of visitors is 4 (which is great news for the home team).
In this example, we'll clean up some of the calculations from Script 3.3 by taking them out of the newCard() function, restating them a bit, and putting them into a function with passed values, in order to make it more obvious what's going on. It all happens in Script 3.4.
To pass a value to a function:
setSquare(i);
This is inside the newCard() function. We're passing the value of i into the setSquare() function.
function setSquare(thisSquare) {
This defines the setSquare() function, and it's being passed the current square number that we want to update. When we pass it in, it's the loop variable i. When the function receives it, it's the parameter thisSquare. What is a little tricky to understand is that this function is passed i, and does stuff with it, but doesn't actually see i. Inside the function, all it knows about is thisSquare.
var currSquare = "square" + thisSquare;
In order to make the getElementById() call later in the script clearer, we're creating and setting a new variable: currSquare. This is the current square that we're working on. It takes the text string "square" and concatenates it with the thisSquare variable.
document.getElementById(currSquare). innerHTML = newNum;
This line gets the element with the name specified by currSquare and changes it to display newNum.
See it in action: Detecting Objects
When you're scripting, you may want to check to see if the browser is smart enough to understand the objects you want to use. There is a way to do this check, which is called object detection.
What you do is pose a conditional for the object you're looking for, like this:
if (document.getElementById) {
If the object exists, the if statement is true, and the script continues on its merry way. But if the browser doesn't understand the object, the test returns false, and the else portion of the conditional executes. Script 3.5 gives you the JavaScript you need, and Figure 3.3: shows you the result in an obsolete browser.
To detect an object:
if (document.getElementById) {
This line begins the conditional. If the object inside the parentheses exists, the test returns true, and the rest of this block in the newCard() function runs.
else {
alert("Sorry, your browser doesn't support this script");
}
If the test in step 1 returns false, this line pops up an alert, and the script ends.
See it in action: Working with Arrays
In this example, we're introducing another useful JavaScript object, the array. An array is a kind of variable that can store a group of information. Like variables, arrays can contain any sort of data: text strings, numbers, other JavaScript objects, whatever. You declare an array with the elements of the array inside parentheses, separated by commas, like so:
var newCars = new Array("Toyota", "Honda", "Nissan");
After this, the newCars array contains the three text strings with the car makes. To access the contents of the array, you use the variable name with the index number of the array member you want to use, in square brackets. So newCars[2] returns "Nissan", because array numbering, like most other numbering in JavaScript, begins at zero.
In this example, shown in Script 3.6, we begin making sure the Bingo card is valid. On a real Bingo card, each column has a different range of numbers: B is 1–15, I is 16–30, N is 31–45, G is 46–60, and O is 61–75. If you look back at Figure 3.1:, you'll see that it is not a valid card, because it was generated with a version of the script that simply put a random number between 1 and 75 in each square. This example fixes that, with only two lines of changed or new code. When we're done, the card will look something like Figure 3.4:. It's still not a valid Bingo card (note how there are duplicate numbers in some of the columns), but we're getting there.
To use an array:
var colPlace = new Array(0,1,2,3,4, 0,1,2,3,4, 0,1,3,4, 0,1,2,3,4, 0,1, 2,3,4);
We're concerned with limiting which random numbers go into which columns. The simplest way to keep track of this is to give each column a number (B: 0, I: 1, N: 2, G: 3, O: 4) and then calculate the numbers that can go into each column as (the column number x 15) + (a random number from 1-15).
However, HTML tables are laid out in row cell order, not column cell order; that is, we're going across the first row, then across the second row, and so on, versus down the first column, then down the second column, and so on. The colPlace array keeps track of, for each square, which column it ends up in. It's the numbers 0-4 repeated five times (minus the free space; notice the sequence in the middle of the array 0,1,3,4 which skips the free space, which would be at position 2).
var newNum = (colPlace[thisSquare] * 15) + Math.floor(Math.random() * 15) + 1;
The newNum variable still generates the random numbers, but instead of coming up with a number from 1-75, it now calculates a random number from 1-15, and then adds that to the column number, colPlace[thisSquare], multiplied by 15—so, if our random number is 7, it would be 7 in the B column, 22 in the I column, 37 in the N column, 52 in the G column, and 67 in the O column.
See it in action: Working with Functions That Return Values
Up to this point, all the functions that you've seen simply do something and then return. Sometimes, though, you want to return a result of some kind. Script 3.7 makes the overall script more understandable by breaking out some of the calculations in previous examples into a function which returns the random numbers for the cells on the Bingo card. Another function then uses this result.
To return a value from a function:
var colBasis = colPlace[thisSquare] * 15;
var newNum = colBasis + getNewNum() + 1;
The first line is just a little bit of cleanup, in that we've calculated it in the last task; now, we're instead assigning it to a variable that we'll use in the second line.
The second line is again just setting the newNum variable to our desired number, but here we've moved that random number generator into a function, called getNewNum(). By breaking the calculation up, it makes it easier to understand what's going on in the script.
function getNewNum() {
return Math.floor(Math.random() * 15);
}
This code calculates a random number between 0 and 14 and returns it. This function can be used anywhere a variable or a number can be used.
See it in action: Updating Arrays
As you saw in Figure 3.4:, the Bingo card script doesn't yet have a way to make sure that duplicate numbers don't appears in a given column. This example fixes that problem, while simultaneously demonstrating that arrays don't have to be just initialized and then read—instead, they can be declared and then set on the fly. This gives you a great deal of flexibility, since you can use calculations or functions to change the values in the array while the script is running. Script 3.8 shows you how, with only a few new lines of code.
To update an array on the fly:
var usedNums = new Array(76);
Here is a new way of declaring an array. We're putting into the usedNums variable a new array with 76 objects. As mentioned before, those objects can be anything. In this case, they're going to be Booleans, that is, true/false values.
if (!usedNums[newNum]) {
usedNums[newNum] = true;
If the newNum slot in the usedNum array is false (represented by the ! before the statement, meaning "not"), then we set it to true and write it out to the card. If it's true, we don't do anything at all, leaving us with no duplicates, but possibly blank spaces on our card (Figure 3.5:). That's not good either, which leads us to the next task.
See it in action: Using Do/While Loops
Sometimes you'll need to have a loop in your code that loops around a number of times, but there's no way of knowing how many times you'll want to loop. That's when you'll want to use a do/while loop: you want to do something, while some value is true. Script 3.9 writes out each row of numbers as always, but this time it checks first to see if a number has been used already before putting it in a cell. If it has, the script generates a new random number and repeats the process until it finds one that's unique. Figure 3.6: shows the working, finally valid Bingo card.
To use a do/while loop:
var newNum;
In the previous task, we initialized the newNum variable when we created it. Because we're going to be setting it multiple times, we're instead going to create it just the once, before we get into the loop.
do {
This line starts the do block of code. One of the things you have to remember about this type of loop is that the code inside the do block will always be executed at least once.
newNum = colBasis + getNewNum() + 1;
This line inside the loop sets the newNum variable to our desired number, as with previous examples.
}
The closing brace signals the end of the do block.
while (usedNums[newNum]);
The while check causes the do block of code to repeat until the check evaluates to false. In this case, we're checking newNum against the usedNums[] array, to see if newNum has already been used. If it has, control is passed back to the top of the do block and the whole process starts again. Eventually, we'll find a number that hasn't been used. When we do, we drop out of the loop, set the usedNums[] item to true, and write it out to the card, as in the last task.
See it in action: Calling Scripts Multiple Ways
Up to this point in the book, you've seen scripts that usually run automatically when the page loads. But in the real world, you'll often want to give the user more control over your scripts, even allowing them to run a script whenever they want. In this example (Script 3.10), the script still runs when the page loads. But we also allow the user to click the link at the bottom of the page to rerun the script that generates the Bingo card entirely in their browser, without needing to reload the page from the server. This gives the user fast response with zero server load. This is the last of the Bingo card examples, wrapping up all the techniques we've used in this chapter to this point.
To call a script multiple ways:
window.onload = initAll;
In previous examples, we called newCard() when onload ran. Now, we're calling a new function, initAll(), which will handle all the required initialization steps.
function initAll() {
if (document.getElementById) {
document.getElementById("reload").onclick = anotherCard;
newCard();
}
else {
alert("Sorry, your browser doesn't support this script");
}
}
The initAll() function just gathers together several of the initialization steps we've previously seen, plus one new thing. If the browser is capable, all it does that's new (besides the object detection and the call to newCard()) is set the link on the HTML page (the one with the id of reload; refer back to Script 3.1) to call anotherCard() when it's clicked. The object detection was moved from newCard() to here as it's really part of the initialization routine, and that's what's handled by this function.
function anotherCard() {
for (var i=1; i<usedNums.length; i++) {
usedNums[i] = false;
}
newCard();
return false;
}
Here's the anotherCard() function that's called when someone clicks the link. It does three things:
Sets all the items in the usedNums[] array to false (so that we can reuse all the numbers again),
Calls the newCard() function (generating another card),
Returns a value of false so that the browser won't try to load the page in the href in the link (this was covered in Chapter 2).