0

Ok, well, thanks to people here, I figured out the portion of my js code that's rendering my links useless is what's pasted below... Still trying to figure out why, but if someone has any insight, that'd be great...

function initJumpMenus() {
    // Turns all <select> elements with the 'jumpmenu' class into jump menus
    var selectElements = document.getElementsByTagName("select");
    for( i = 0; i < selectElements.length; i++ ) {
        // Check for the class and make sure the element has an ID
        if( selectElements[i].className == "jumpmenu" &&
            document.getElementById(selectElements[i].id) != ""
        ) {
            jumpmenu = document.getElementById(selectElements[i].id);
            jumpmenu.onchange = function() {
                if( this.options[this.selectedIndex].value != '' ) {
                    // Redirect
            location.href=this.options[this.selectedIndex].value;
                }
            }
        }
    }
}

window.onload = function() {
    initJumpMenus();
}
1
  • Don't use <select> “jump menus” for navigation. Usability is poor (especially for keyboard users), they would need accessible backup, and they interact badly with the back button. A pop-up div full of links is more appropriate.
    – bobince
    Commented Jan 12, 2010 at 14:08

2 Answers 2

3

This:

document.getElementById(selectElements[i].id) != ""

is wrong. You want to check if the element has an id, so simply do:

selectElements[i].id != ""

I'd like to point out that you can improve your code here and there:

1 Don't write for loops that get the length property for each iteration,

instead do:

for( i = 0, num = selectElements.length; i < num; i++ ) {
    ...
}

Only if you expect selectElements to grow or shrink it would make sense to requery the property value, but in that case, you probably should not write a for loop anyway.

2: Don't write nodelist[index]

For nodelists, such as returned by getElementsByTagName() you should not write nodelist[index] (even though most browser support that). The standard DOM method is item, so write nodelist.item(index) instead.

3 fetch items from the list only once

If you need an item from the list more than once, store it in a local variable. your loop would become:

for( i = 0, selectElement; i < selectElements.length; i++ ) {
    selectElement = selectElements.item(i);
    ...more code using selectElement...
}

Note the declaration of the selectElement variable in the for loop. since you don't use it outside the loop, declaring it there avoids clutter and ensures that if you move the loop, you move the declaration.

4. cheapest comparisons first

You wrote:

selectElement.className == "jumpmenu" &&
selectElement.id != ""

this could be slightly improved if you reverse the legs:

selectElement.id != "" &&
selectElement.className == "jumpmenu"

This will be faster, since it is less work to check if a string is empty, and for those cases where the string is empty, we won't even check the className

5 Don't use document.getElementById() if you already have the element

Inside the loop you have this:

jumpmenu = document.getElementById(selectElements[i].id);

You are basically getting the id from the selectElement and use that to query the document to find ....the element having and id equal to that of the current selectElement. Becuase id's are unique within the document (or should be), you are basically writing a completely unecessary sentence. jumpmenu and selectElement refer to one and the same object.

6. onchange handler improvements

inside the loop you assign an onchange handler. you do so by creating a new function for each loop iteration. This is the handler code:

function() {
    if( this.options[this.selectedIndex].value != '' ) {
        // Redirect
        location.href=this.options[this.selectedIndex].value;
    }
}

Three things are of note here: First, the onchange handler code contains this location.href = ... that should probably be document.location.href = ....

Second, you refer twice to this.options[this.selectedIndex].value. Again, put this in a local variable.

Third, the this refers to the element that experienced the onchange event by the time this function is executing. Other than this and properties of this, there are no variables in this handler that originate from the loop or the outer initJumpMenus function. You should simply create it once, outside the loop, and assign it every iteration:

var onchange_handler = function() {
    if( this.options[this.selectedIndex].value != "" ) {
        // Redirect
        location.href=this.options[this.selectedIndex].value;
    }
}
for (...) {
    if (...) {
        selectElement.onchange = onchange_handler;
    }
}

7 Summary

Putting it all toghether, this is how I would write it:

function initJumpMenus() {
    var handler = function() {
        var value = this.options.item(this.selectedIndex).value;
        if( value != "" ) {
            // Redirect
            document.location.href = value;
        }
    }
    var selectElements = document.getElementsByTagName("select");
    for(var i = 0, num = selectElements.length, selectElement; i < num; i++ ) {
        selectElement = selectElements.item(i);
        // Check for the class and make sure the element has an ID
        if( selectElement.id != "" &&
            selectElement.className == "jumpmenu"
        ) {
            selectElement.onchange = handler;
        }
    }
}
4
  • 1
    You're missing a definition of selectElements, and several var declarations (unintended globals). The handler could also stand to be outside the function to avoid an unnecessary closure (and memory leak in IE6-7). I'm not convinced the micro-optimisation nitpicks are of value. IMO Readability first, performance hacks when you need them. You can also certainly rely on indexing HTMLCollection[i] in every JavaScript browser there has ever been.
    – bobince
    Commented Jan 12, 2010 at 14:04
  • @bobince - thanks, missing declarations fixed. RE micro optimizations: the only microoptimization i see is the reversal of the checks in the &&. We can agree to disagree whether storing selectElements.length in a local is good or bad, but storing this.options.item(this.selectedIndex).value in a local improves readability quite substantiably so I don't see how you can disapprove of that. RE the closure: perhaps I am mistaken, but I thought it would need to have free variables before it's called a closure. I thought this handler was merely anonymous. Commented Jan 12, 2010 at 14:16
  • As helpful as some of these points may be, I read this answer as more of a lecture than anything else. Point 4 is absolutely a micro-optimisation. And to re-iterate what bobince said, I haven't seen a javascipt implementation which doesn't support array-style collection indexing.
    – Andy E
    Commented Jan 12, 2010 at 19:21
  • @Andy: my apologies if you feel the answer is condescending - I didn't intend it. But when I read the code in the OP I got the impression that problems like document.getElementById(selectElements[i].id) != "" could easily be avoided by becoming a bit more aware of what the code actually does. In my experience, avoiding repetition of complex constructs like this.options[this.selectedIndex].value result in code that is more readable, and less buggy. I'm happy to agree with you and bobince that point 4 is a microoptimization. (but I don't think its one that hurts or makes it less readable) Commented Jan 12, 2010 at 19:45
0

Check the error console; any exception in the JS will stop the links form reacting to a click.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.