Section H. Control structures


H. Control structures

In the previous section we took a quick peek at the if statement. It's time to treat it, and its fellow control structures, officially.

The if statement

An if() statement always needs an expression between its parentheses. This expression is interpreted as a boolean value. If the expression is true, then the if's script block is executed; if the expression is false, then the script block isn't executed:

if (this expression returns true) {     // this block is executed } 


For instance:

if (x == 4) {        alert('x is 4. Oh joy!'); } 


Common Errors in If Statements

As we saw in 5A, there are two nasty little errors to avoid in your if statements. Both examples below will always show the alert, even if x is not 4:

if (x = 4) {   alert('Hurray'); } if (x == 4); {   alert('Hurray'); } 


The first example does not compare x to 4, but assigns 4 to x and also returns this value. Since 4 converts to boolean true, the alert always pops up. The second example has a semicolon in exactly the wrong place, and therefore says "if x is equal to 4, do nothing. Afterwards, show the alert."


The comparison x == 4 returns a boolean true or false. If true is returned, the alert pops up.

Now take this if statement from Textarea Maxlength. When the script initializes, it goes through all textareas in the page and sees if they have an attribute maxlength. If one does, the textarea is initialized:

[Textarea Maxlength, lines 10-19, condensed heavily]

if (textareas[i].getAttribute('maxlength')) {     // initialize textarea } 


textareas[i] is the current textarea. I use the getAttribute() method to search for the attribute maxlength. If the attribute exists, then getAttribute() returns its value, which is a string, such as "300". If there is no maxlength attribute, it returns null.

The if statement interprets this returned value as a boolean, which is false if the value is null, and true otherwise. Therefore the textarea is initialized only if it has a maxlength attribute.

You can also use several boolean operators within the if statement. This allows you to perform quite complicated checks. For instance, take a look at the most complex if statement in the example scripts, from Usable Forms:

[Usable Forms, lines 70-81, condensed]

var e = [event object]; var tg = [target of event]; if (!(     (evtTarget.nodeName == 'SELECT' && e.type == 'change')     ||     (evtTarget.nodeName == 'INPUT' && evtTarget.getAttribute('rel')) )) return; 


If the user clicks anywhere on the page or changes any select box, a function is called. I want it to actually do something in only two cases:

  • If the user has changed a select box, i.e., a change event has fired and its target is a select box.

  • If the user has clicked on an input that has a rel attribute.

If neither is the case, the function should end immediately (return), and that's what the if checks for. Let's take it apart:

(tg.nodeName == 'SELECT' && e.type == 'change') // case 1 


This bit is true if the target of the event is a <select> element AND the type of the event is change. Let's call it 'case 1'.

(tg.nodeName == 'INPUT' && tg.getAttribute('rel')) // case 2 


This bit is true if the target of the event is an <input> element AND it has an attribute rel. Let's call it 'case 2'. So the if becomes:

if (!(case 1 || case 2)) return; 


If both cases are false, the expression says:

if (!(false || false)) return; 


false || false is false, so it becomes:

if (!false) return; 


The ! operator reverses this value to true. Therefore, the function ends if neither of my requirements is met. That's exactly what I want.

Now suppose case 2 is true: the user has clicked on an <input> with a rel attribute. The expression says:

if (!(false || true)) return; 


or:

if (!true) return; 


Again, the ! operator reverses this value to false. Now the return statement is not executed and the function does not end. That's also exactly what I want.

else if and else

if statements can be extended with else if or else. Both are executed only when the original if statement is not executed. else if means "Now try this if," while else means "In all other cases."

For instance:

if (x == 4) {     alert('x is 4. Oh joy!'); } else {     alert('x is not 4. Too bad.'); } 


If x is equal to 4, the first code block is executed and the second one is ignored. If x is not equal to 4, the first code block is ignored and the second one is executed.

else if works like this:

var x = 2; var y = 3; if (x == 4) {     alert('x is 4. Hurray!'); } else if (y == 3) {     alert('y is 3. Hurray!'); } 


The first condition x == 4 is false: x is not equal to 4. Therefore, the first code block is not executed. JavaScript now moves to the else if and evaluates the condition y == 3. Since it is true, the second alert is shown.

Of course you can use as many else ifs as you like. Take this example, again from Usable Forms:

[Usable Forms, lines 85-99, condensed]

if (evtTarget.type == 'checkbox') {     // handle checkbox } else if (evtTarget.type == 'radio') {     // handle radio } else if (evtTarget.type == 'select-one') {         // handle select box } 


If the event target is a checkbox, the first block is executed. If it's a radio, the second one is executed, and if it's a select box, the third one is executed.

switch

I could also have used a switch statement for the last code example.

The switch statement takes a value and goes through a list of options, each of which is called a case. If it finds the right case, it starts executing the code block and doesn't stop until it reaches the end of the switch block or a break statement. It also ignores all other cases.

Take this bit from Edit Style Sheet. It isn't that different from the previous if/else if example:

[Edit Style Sheet, lines 74-86]

switch (relatedField.type) {     case "text":            relatedField.value = styles[i];            break;     case "checkbox":            if (relatedField.value == styles[i])                   relatedField.checked = true;            break;     case "select-one":            for (var j=0;j<relatedField.options.length;j++)                   if (relatedField.options[j].value == styles[i])                          relatedField.options[j].selected=true; } 


The switch is performed on the value of relatedField.type, which, once again, gives the type of a form field.

If type is "text", the first line is executed, and then the switch encounters a break statement, which means "Stop operation." If type is "checkbox", the next two lines are executed, and a break is once again encountered. If type is "select-one" the last few lines are executed. There's no break statement after those lines because it's not necessary: the entire switch block ends.

No Data-Type Conversion

Note that the value after the case must have the same data type as the variable you use in the switch; no data-type conversion takes place. Take this code:

var x = '4'; switch (x) {   case 4:           alert('x is 4. Oh joy!');           break; } 


The alert will not pop up, since the case expects the number 4, not the string '4'. This is especially important when you use form-field values, which are always strings, even when they contain only numbers.


Common error

When your code is still in development, it's a good idea to insert the last break anyway, in order to avoid the most common error in switch statements. Suppose later on you add another case but don't pay enough attention to the existing code:

case "select-one":     for (var j=0;j<relatedField.options.length;j++)            if (relatedField.options[j].value == styles[i])                   relatedField.options[j].selected = true;     // note: no break statement case "submit":     // handle submit 


Now if type is select-one, the switch executes the select-one case and then continues to the 'submit' case, since there is no break statement to stop it. This is usually not what you want.

Deliberately omitting a break

Sometimes, however, omitting a break can be exactly what you want. Take this example from Form Validation:

[Form Validation, lines 15-32]

function isRequired(obj) { // obj is the form field     switch (obj.type) {             case 'text':             case 'textarea':             case 'select-one':                    if (obj.value)                            return true;                    return false;             case 'radio':                    var radios = obj.form[obj.name];                    for (var i=0;i<radios.length;i++) {                            if (radios[i].checked) return true;                    }                    return false;             case 'checkbox':                    return obj.checked;     } } 


I use a return statement instead of break, but the return serves the same purposeit ends the case.

Note the start of the function:

case 'text': case 'textarea': case 'select-one':     if (obj.value)             return true;     return false; 


The form-field types ("text", "textarea", and "select-one") should be treated the same: we need to check if fields of those types have a value. Therefore, the three case statements all refer to the same bit of code.

For example, if the type of the form field is text, the function starts at case: text, and then continues until it finds a return (or break). Along the way, the function encounters two other cases, but it ignores those linesthey are irrelevant, since the case is text.

default

Neither example contains the final feature of a switch: the default statement. default is a case, too, and it simply means "In all other cases."

It's used as follows:

function isRequired(obj) { // obj is the form field     switch (obj.type) {             case 'text':             case 'textarea':             case 'select-one':                     // handle             case 'radio':                     // handle             case 'checkbox':                     // handle             default:                     alert('Sorry, I don\'t know what to do');     } } 


If obj.type has none of the values defined in the case statements, the switch continues on to the default and executes it.

for, while, do/while

There are four loop statements in JavaScript, and although they differ in detail, they are all meant to repeat a code block until a certain condition is met. The most common one is the for loop, but occasionally you'll use while and do/while, too.

The fourth one, for in, loops through the properties of an object. Since it is hard to understand without treating JavaScript objects and associative arrays first, I will explain it in detail in 5K.

for

This is the structure of a for loop:

for (initialize; test; update) {     statements; } 


A for statement always needs three arguments: an initial condition, a test (will the for continue for another loop?), and an update. The statements in the block are run repeatedly until the test becomes false.

By far the most common use of for is something like this:

[Dropdown Menu, lines 7-19, condensed heavily]

var lists = document.getElementsByTagName('ul'); for (var i=0;i<lists.length;i++) {     if (lists[i].className != 'menutree') continue;     // initialize <ul> } 


The loop goes through all <ul> elements in the page and does something with all of them. (We'll get back to the continue statement later.)

Let's take a closer look at the three arguments:

  1. var i=0 is the initial condition. When the loop starts up for the first time, a local variable i is created and set to 0.

  2. i<x.length is the test. Initially, and whenever the update has been performed, the for statement checks the test, and if the test returns true (if i is still smaller than x.length), it continues for another loop.

  3. i++ is the update. Every time the for statement finishes a loop, it performs the update. i is increased by 1.

The net result of all this is that the for loop goes through all <ul>s in the document. This is a best practice: as soon as you use getElementsByTagName(), (see 8B), you start up a for loop to go through the nodeList it returns.

while

A while loop takes only one argument: a test expression. The loop continues as long as the expression remains true.

I use while loops mainly when I want to go through an array and discard its elements one by one after doing something with them. Take this example from Usable Forms:

[Usable Forms, lines 41-51, condensed heavily]

while (hiddenFields.length) {     // do stuff with hiddenFields[0]     waitingRoom.appendChild(hiddenFields.shift()); } 


hiddenFields is an array of <tr>s that should be removed from the document. The while loop takes the first of these <tr>s and removes it from the document. It simultaneously removes this <tr> from the array by means of the shift() method. We'll get back to this in 8I.

Once per iteration, the while loop tests for hiddenFields.length: in other words, it tests if the array still has elements in it. Once the length reaches 0 (which evaluates to false), the while loop quits.

In theory it's possible to rewrite all for loops as while loops. For instance, here's the for loop example rewritten as a while loop:

var lists = document.getElementsByTagName('ul'); var i = 0; while (i<lists.length) {     if (lists[i].className == 'menutree') {            // initialize <ul>     }     i++; } 


There's a danger here, though. i must be updated within the while loop, or the loop will continue forever. The i++ does that, but I had to make sure it was executed every time, which means I had to change the class-name check.

Suppose I'd done it wrong:

   var lists = document.getElementsByTagName('ul');  while (i<lists.length) {      if (lists[i].className != 'menutree') continue;      // initialize      i++;    } 


Say the second <ul> does not have class="menutree". The while loop faithfully executes the continue statement, but i is not updated. That means that the next iteration of the loop again checks the second <ul>, which still doesn't have a class="menutree", and i is again not updated. This continues indefinitely. (Fortunately, most browsers recognize an infinite loop after a while, and allow the user to break it.)

In general, when using a while loop, you have to make sure that every iteration updates your test variable.

do/while()

The do/while() loop is almost, but not quite, the same as while(). It, too, takes one test expression and continues to loop as long as the expression remains true. The difference is that where while() first checks the test expression and then starts another loop, do/while() first performs a loop and then checks the test expression.

Take this example:

var x = 0; while (x != 0) {     alert('x is not 0'); } 


The while() loop is never executed, since the very first test reveals that x is 0.

var x = 0; do {     alert('x is not 0'); } while(x != 0); 


However, the do/while() loop executes before any test takes place and shows an alert. Only after the alert is shown does the test reveal that x is 0, and the loop stops.

As with while loops, you should make sure that the test variable in do/while loops is updated in every loop.

Semicolons and Do/While

You'll notice that I added a semicolon after the do/while loop. In this position it's perfectly safe, since the code block has already been defined. Semicolons are only dangerous when they're inserted between an if, for, or while statement and the code blocks they're supposed to execute.


So any code in a do/while() loop is executed at least once, while code in a while() loop may never be executed.

The animation functions of XMLHTTP Speed Meter contain a useful example of do/while(). I have to create an animation that goes from one point to another, but beforehand I have no idea what these two points will be, and whether the animation moves to the left or to the right.

Superficially, using a for loop seems to be possible. currentMbit is the current position, and Mbit the desired position:

[XMLHTTP Speed Meter, lines 79-95, condensed and changed]

function moveToNewSpeed(Mbit) {     var distance = Mbit - currentMbit;     var direction = distance/Math.abs(distance);     if (!distance) return;         for (var pos = currentMbit;pos != Mbit;pos+=direction) {             // give animation orders         } } 


The script first calculates the distance between the two points, and the direction the animation has to travel (+1 or -1). Then the variable pos goes from currentMbit to Mbit in steps of direction. pos now becomes all intermediate steps of the animation, and this is used to give the animation orders.

This seems to work fine, except for one tricky bit: the very last step, when pos becomes exactly equal to Mbit, is never made. As soon as pos becomes equal to Mbit, the test expression pos != Mbit becomes false, and the for loop stops. The animation stays stuck at the penultimate step.

I could solve the problem by changing the test expression to pos < Mbit or pos > Mbit. However, I don't know whether pos starts out as being larger or smaller than Mbit, because I don't know in which direction the animation is going. If I use pos < Mbit, animations from left to right would never work, and if I use pos > Mbit, animations from right to left would never work.

Therefore I use a do/while() loop:

[XMLHTTP Speed Meter, lines 79-95, condensed]

function moveToNewSpeed(Mbit) {     var distance = Mbit - currentMbit;     var direction = distance/Math.abs(distance);         do {             pos += direction;             // give animation orders         } while (pos != Mbit) } 


Now the test pos != Mbit is executed after every loop instead of before it. When we reach the point where pos is equal to Mbit, the loop first executes the final step of the animation. Only when that's done does it conclude that it should stop.

break and continue

We already encountered two statements related to loops: break and continue.

break

The break statement can be used only within a switch or a loop statement. It means "End this code block immediately." It always refers to the innermost code block of which it's a part.

I use it in Form Validation. When I find an error in a form field I immediately want to create an error message and go on to the next form field:

[Form Validation, lines 85-96, condensed]

for (var j=0;j<reqs.length;j++) {     var OK = validationFunctions[reqs[j]](x[i]);     if (OK != true) {            // generate error message            break;     } } 


The break statement works on the for loop. This loop goes through all values of the validation attribute, and each of these values has an associated function (see 5K).

If that function returns an error, an error message is generated. If that happens, the script breaks the for loop, because I don't want to continue with the other validations; more than one error message would only confuse the user.

As we've seen, break has a slightly different function in the switch statement.

continue

The continue statement can only be used in a loop, and it means "Go on to the next iteration." It always refers to the innermost loop of which it's a part.

Take this example:

[Dropdown Menu, lines 7-19, condensed heavily]

var lists = document.getElementsByTagName('ul'); for (var i=0;i<lists.length;i++) {     if (lists[i].className != 'menutree') continue;     // initialize <ul> } 


The script takes all <ul> tags in the document and loops through them. If the class name of a <ul> is not 'menutree', then it doesn't contain a dropdown menu, and the script must continue with the next <ul>. The continue statement does this: it skips the rest of the code block, goes back to the for statement, performs the update and the test, and continues with the next <ul>.

Labels

Occasionally you have nested loops and want to continue one that isn't the innermost one. For instance:

for (var i=0;i<x.length;i++) {         for (var j=0;j<x.length;j++) {             if ([something is the case]) continue;     } } 


The continue statement now continues the inner loop (the j) because that is its default behavior. If you want it to continue with the outer loop (the i), you have to use a label:

outerloop: for (var i=0;i<x.length;i++) {        for (var j=0;j<x.length;j++) {            if ([something is the case]) continue outerloop;     } } 


As you see, a label is nothing more than a bit of text plus a colon :. Repeating the bit of text after the continue tells JavaScript which loop to continue. I used the label text 'outerloop' in the example because it refers to the outer loop, but you can use any name you like.

You can also use a label after a break statement; it specifies which loop should break. Although you're allowed to add labels to any JavaScript statement, they're only really useful in combination with continue or break.

try/catch

The try/catch statement is not a loop. Its purpose is to make sure the user never sees an error message. It tries a few lines of code, and if they generate an error, the catch statement kicks in.

The general syntax is as follows:

try {     // these lines may cause an error } catch (e) {     // these lines are executed if an error occurs } finally {     // these lines are always executed regardless of any errors } 


You may leave out the catch or the finally block if you don't need them. Note the (e) in the catch statement: it's required in some browsers. Using the variable name e is traditional, but you may use another name.

I am not a big fan of try/catch statements, since I don't like executing code that may cause an error. Nonetheless, it's sometimes necessary to use them. Site Survey contains an example:

[Site Survey/popup.js, lines 77-87, condensed]

function checkCommunication() {     try {            opener.testVar = true;     }     catch (e) {            return false;     }     return true; } 


For reasons we'll discuss in 6B, the opener.testVar = true line may cause an error. The purpose of this function is to find out if that happens.

The function tries to execute the offending line. If that generates an error message, the catch statement executes and returns false. If the line works, the catch statement is ignored, and the function continues with the return true.

10A discusses another try/catch statement in XMLHTTP Speed Meter.



ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ppk on JavaScript. Modern, Accessible, Unobtrusive JavaScript Explained by Means of Eight Real-World Example Scripts2006
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net