Forcing the min/max on HTML number inputs

HTML5 has fantastic new features — one of them is type="number" attribute for input tags, which (among other things) restricts the user to only entering numbers (and spawning the number keyboard on mobile phones) and giving a (not always useful) widget that lets you increase/decrease the number. Unfortunately, one piece is missing (at least from most browsers I’ve seen): restricting the input to numbers within the min/max range.

Fortunately, a little JavaScript (via jQuery) can fix that. Add the below to your site, and any input type="number" tags on your site will have their min/max values enforced.
Continue reading

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

Styling your checkboxes and radio buttons

A fantastic Web page called Styled Checkboxes describes how to use CSS and JavaScript to create graphical checkboxes and radio buttons that depreciate to the standard checkbox and radio button HTML controls when CSS or JavaScript is turned off. It works, too – and is definitely worth checking out as a way to pretty up your Web pages.

A unique idea? Not really, but it’s one of the first times I’ve seen it written up and packaged in one place.

Friendly Session Timeouts, the JavaScript Way

A client recently asked me if there was any way to notify visitors to their Web site that their session was about to expire. Fortunately, there’s an easy way to do this using JavaScript.

First, add the following code block to your standard JavaScript include file. (You are using an included file for your JavaScripts, aren’t you?) Essentially, you want the following piece of JavaScript code to be available on each page:

function ShowTimeoutWarning ()
{
    window.alert( "You will be automatically logged out in five minutes unless you do something!" );
}

That little function does nothing more than show a popup message to the user. The text of the message (and the five minutes) duration is arbitrary; you can make them whatever you want.

How will we invoke the popup? By using a simple JavaScript function called setTimeout. Pass two parameters to this function: the name of another JavaScript function to call, and the amount of time to wait (in milliseconds) before calling that function. I add the following line into the HTML <HEAD> section within a <SCRIPT> tag.

setTimeout( 'ShowTimeoutWarning();', 900000 );

90,000 milliseconds is 15 minutes (15 minutes * 60 seconds per minute * 1000 milliseconds per second), so in 15 minutes the ShowTimeoutWarning() function will be called automatically. Of course, if the user unloads this Web page (by going to another Web page), the timer disappears.

That 15 minute threshold for the timer was based on five minutes less than the 20 minute session timeout in the Web application.

But what if your Web site (like my client’s Web site) had different session timeouts for different users? You can use a little in-line .Net code to dynamically calculate the timeout period. Here’s how I did it for my client — by adding the following lines within a <SCRIPT> tag:

if ( HttpContext.Current.User.IsInRole( "User" ) )
{
    Response.Write( "setTimeout('ShowTimeoutWarning();', " +
        ( ( Session.Timeout - 5 ) * 60000 ).ToString() + " );" );
}

Notice how I calculated the timeout dynamically by using the Session object’s Timeout property. Session.Timeout is stored in minutes, so subtract your announcement time (5 minutes) from that number, then multiply by 60,000 (milliseconds per minute) to get your user-customized timeout alert.