Working Around IE Bugs: Rebuilding Select Boxes in JavaScript

Recently I was building a simple web application form where one of select boxes was driven by the value of a different box. The original code for this seemed pretty straightforward, but while the code worked perfectly in Firefox and Chrome, it was producing amazingly strange behaviour in Internet Explorer. Namely, when I’d rebuild the select box, IE would not position the text correctly inside of it, the dropdown wouldn’t appear reliably, and when it would, it wouldn’t necessarily show the correct text for the selections. In one case it was actually displaying four selections, when there were in fact only two. You could use the keyboard to select the values correctly, but this was still absolutely unacceptable behaviour.

I’ll do a write up on the behaviour later this week, but I decided to jump the gun and present the solution I developed first, since I don’t have good simple examples just yet. I ended up trying several ways of creating the options before appending them to the select element, but eventually, I had to completely recycle the selectbox in order to make it behave correctly. I’ve had other pages that didn’t misbehave in quite this way, so I’m trying to figure out the minimum case to recreate the problem before I send it to Microsoft.

Luckily, JavaScript makes it pretty easy to branch code based on browser in a way that doesn’t require doing the browser check all the time. Below is a generic version of the function to do the replacement, which includes calling a ‘change’ event handler, which unfortunately can’t be anonymous (at least in YUI3) since ‘change’ events can’t be simulated. Actually, having to rebind the changeeventhandler every time you change the contents of the select box is the only possible ‘challenge’ in the problem.

var rebuildSelectBoxOptions = Y.UA.ie > 0 ? function(box, options, change_event_handler) {
    var newOption, newSelector;
    newSelector = Y.Node.create(box.set('innerHTML', '').get('outerHTML'));
    for (var i = 0; i < options.length; i += 1) {
        newSelector.append('<option value="' + options[i].value + '">' + options[i].text + '</option>');
    }
    newSelector.set('selectedIndex', 0);
    box.replace(newSelector._node);
    if (Y.Lang.isFunction(change_event_handler)) {
        change_event_handler({target: newSelector});
        newSelector.on('change', change_event_handler);
    }
} : function(box, options, change_event_handler) {
    box.set('innerHTML', '');
  for (var i = 0; i < options.length; i += 1) {
        box.append('<option value="' + options[i].value + '">' + options[i].text + '</option>');
    }
    box.set('selectedIndex', 0);
    if (Y.Lang.isFunction(change_event_handler)) {
        change_event_handler({target: box});
    }
};

The non-IE version is a couple of hundred bytes lighter, and should be faster (though for most use cases, the difference is likely negligible). I’m not sure yet what the increased garbage collection load of this will do to IE, but in my case, that’s not likely to be a problem.