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);
});

Firefox, LinkButtons, and the Panel.DefaultButton: a (Prototype) fix

Recently I’ve stepped away from the MonoRail world to work on a project that uses ASP.Net WebForms. It didn’t take long before I found an annoying problem. (Actually I found many annoying problems, but I’ll focus on one here.)

The <ASP:Panel> control has a DefaultButton property which, according to the documentation, "Gets or sets the identifier for the default button that is contained in the Panel control." In other words:

Use the DefaultButton property to indicate which button gets clicked when the Panel control has focus and the user presses the ENTER key.

It works perfectly, if you’re not using a LinkButton control. Actually, that’s not true; if you use a LinkButton control and Internet Explorer, it works fine. It just doesn’t work in Firefox. Why?

Dmytro Shteflyuk outlines why in his blog post, Using Panel.DefaultButton property with LinkButton control in ASP.Net. Apparently, it’s an issue with the JavaScript code that Microsoft generates, which expects a click() method on the anchor (i.e. LinkButton). Firefox doesn’t have such an event for anchors — at least, not by default.

Dmytro outlines a fix which requires you to injext some JavaScript for each button. I prefer a simpler approach using CSS selectors, so I wrote the following script (Prototype required) to do it. Simply add this script to your web page, add the CSS class "button" to each LinkButton control, and press ENTER on your Firefox forms.

function prepareLinkButtonClicks()
{
	$$('a.button').each(function(tag) {
		if (tag && typeof(tag.click == 'undefined')) {
			tag.click = function() { 
				var result = true;
				if (tag.onclick) result = tag.onclick();
				if (typeof(result) == 'undefined' || result) {
					eval(tag.href);
				}
			}
		}
	});
}
Event.observe(window, 'load', prepareLinkButtonClicks);