Select/input combo box using Prototype

I was looking around for a good HTML combo box (a drop-down list with a type-in text box), and found a good looking one over at Particletree (see Update Your Select Element to a Combo Box). There were a few quirks I found with it.

  • The text box used absolute positioning. As a result, if the location of the select element changed (perhaps due to another item on the page being shown or hidden), the text box would be in the wrong place.
  • The code was free-standing and applied event handlers directly, instead of using an event manager like one offered in Prototype‘s Event class.

To clean things up, I modified the code to work with the Prototype JavaScript library, converting element selectors and event management. I also added a hack to handle positioning issues by calling a routine that repositions the text box every 0.05 seconds. Though far from efficient, it didn’t cause any flickering or processor utilization. If JavaScript had an “onpositionchanged” event it would be a lot easier.

Either way, below is the modified JavaScript code. Let me know how it works for you, and remember that 99% of it should be attributed to Ryan Campbell of Particletree.

var nTop;
var nLeft;
var detect = navigator.userAgent.toLowerCase();

function setCombobox(bMethod) {
$$('select.comboBox').each(function(combo) {
positions = Element.positionedOffset(combo);
nTop = positions[1];
nLeft = positions[0];

if(bMethod == true) {
inittextfield(combo);
//Use iframe hack for Internet Explorer
if(!(detect.indexOf("opera") + 1) && (detect.indexOf("msie") + 1)) {
initIframe(combo);
}
}
else {
textfield = $("txt" + combo.name);
textfield.style.top = nTop + "px";
textfield.style.left = nLeft + "px";
if((detect.indexOf("msie") + 1)) {
hackFrame = document.getElementById("frame" + combo.name);
hackFrame.style.top = nTop + "px";
hackFrame.style.left = nLeft + "px";
}
}
});
}

// ------------------------------------------------------------------ //
// Create the textfield and move it to desired position               //
// ------------------------------------------------------------------ //

function inittextfield(ctrl) {

selectWidth = ctrl.offsetWidth;

//Create textfield
textfield = document.createElement("input");
textfield.id = "txt" + ctrl.name;
textfield.className = "comboText";
textfield.style.zIndex = "99999";

textfield.value = "";
textfield.style.color = "#ccc";

textfield.style.position = "absolute";
textfield.style.top = nTop + "px";
textfield.style.left = nLeft + "px";
textfield.style.border = "none";

//Account for Browser Interface Differences Here
if((detect.indexOf("safari") + 1)) {
selectButtonWidth = 18
textfield.style.marginTop = "0px";
textfield.style.marginLeft = "0px";
}
else if((detect.indexOf("opera") + 1)) {
selectButtonWidth = 27;
textfield.style.marginTop = "4px";
textfield.style.marginLeft = "4px";
}
else {
selectButtonWidth = 27;
textfield.style.marginTop = "2px";
textfield.style.marginLeft = "3px";
}

textfield.style.width = (selectWidth - selectButtonWidth) + "px";
ctrl.parentNode.appendChild(textfield);
ctrl.onchange=function() {
val = this.options[this.selectedIndex].value;
document.getElementById("txt" + this.name).value = val;
}
ctrl.onfocus=function() {
document.getElementById("txt" + this.name).style.color = "#333";
}
textfield.onfocus=function() {
this.style.color = "#333";
}
}

// ------------------------------------------------------------------ //
// Internet Explorer hack requires an empty iFrame.  We need to add   //
// one right underneath the div -> it will make the zindex work       //
// ------------------------------------------------------------------ //

function initIframe(ctrl) {
textWidth = textfield.offsetWidth;
textHeight = textfield.offsetHeight;
hackFrame = document.createElement("iframe");
hackFrame.setAttribute("src", "placeHolder.html");
hackFrame.setAttribute("scrolling", "0");
hackFrame.setAttribute("tabindex", "-1");
hackFrame.id = "frame" + ctrl.name;
hackFrame.style.position = "absolute";
hackFrame.style.width = textWidth + "px";
hackFrame.style.height = textHeight + "px";
hackFrame.style.top = nTop + "px";
hackFrame.style.left = nLeft + "px";
hackFrame.style.marginTop = "3px";
hackFrame.style.marginLeft = "3px";
ctrl.parentNode.insertBefore(hackFrame, textfield);
}

Event.observe(window, 'load', function() {
setCombobox(true);
new PeriodicalExecuter(function(pe) {
setCombobox(false);
}, 0.05);
});
Event.observe(window, 'resize', function() {
setCombobox(false);
});