
Consider the Web form shown to the left. By default, the way HTML input works, the user needs to explicitly tab out of each textbox to enter each part of the telephone number. Often, especially for heads-down data entry, a desirable feature is to have your applications' textboxes automatically move focus to the next textbox when all the possible characters been typed into any a textbox--let's call it "auto field advance." Auto field advance is how a 5250 display file behaves. Heads-down data entry operators swear by this feature.
This article presents a way to enable auto field advance in your browser-based applications. By including a small JavaScript file in your ASPX pages, and adding just a few lines of code to your pages' code behind class, you can hook this feature up. Do note that any technique for implementing auto field advance in a Web app is an approximately science at best--there is a lot more to auto field advance than you might first think. The solution presented here may not work for all situations; test it thoroughly in your environment before using it.
Doing it. Almost.
My first thought was to use the onchange event to track a textbox's value. When the value reaches the maximum length, throw focus to the appropriate control. Sigh. I found out very quickly that doesn't work. The order of events is keydown, keyup, then onchange. And onchange doesn't get raised until the control loses focus. So, for example, if you press the 's' key and hold it down, 's' are continually piped into the control until you let up on the key. It was starting to look to me like onkeyup would be a start of the solution.
With just a little more effort, I thought I had the operation with this single JavaScript function that was tied to a textbox's onkeyup event. This function, fieldAdvance(), gets passed two parameters each time the keyup event fires for a textbox. This function is passed a reference to the currently focused textbox and the control ID of the control to focus if the control's input has reached its maximum. The currently value's length is checked against what its maximum input length is (as specified by the maxLength attribute--which is set implicitly when you set an ASP.NET's textbox's MaxLength property). If the two are equal, the operator has entered enough text and it's time to move to the nex control.
function fieldAdvance( ctrl, ctrlIdToFocus ) {
var ctrlToFocus = document.getElementById( ctrlid );
if ( ctrl.value.length == ctrl.maxLength ) {
ctrlToFocus.focus();
}
}
}
The following code associates a textbox's onkeyup event to the fieldAdvance function. this means "this" control (that is, the control that owns the onkeyup event being specified) and the 'txtAreaCode' argument specifies the control ID of the control to receive focus when txtAreaCode has received three characters of input.
<input type="text" id="txtAreaCode"
maxLength="4" onkeyup="fieldAdvance( this, 'txtPrefix' );">
Alas, the code above almost works. However, it has two quite substantial problems:
- It doesn't take into account the PC keyboard's ability to buffer keystrokes. That is, if you press a key down, and hold it down, that "piping" in of keystrokes, even with a maxLength attribute specified, causes problems--especially after leaving the control and then tabbing back into it.
- It doesn't let you tab or back tab into a previously filled-out field. When you attempt to, that implicitly fires the onkeyup function, which checks the length of the control's text and moves focus to the control specfied. Thus, when a user tabs into a populated textbox, focus is immediately passed to the next control. There, without some changes, no way to change a value with the method presented.
Making it work. Really.
To make things work required a little more complexity that I had originally hoped for. Here is the full JavaScript that implements auto field advance. A narrative follows the code below.
var globalKeyCode;
var currentFieldid, currentFieldMaxLength, nextFieldid;
var TAB_KEY = 9;
var BACK_TAB_KEY = 16;
var ONE_MILLISECOND = 1;
function registerTemporaryFocusControl( ctrlid ) {
TemporaryFocusControl = ctrlid;
}
function fieldAdvance( ctrl, ctrlToFocusid ) {
var ctrlToFocus = getControl( ctrlToFocusid );
this.currentFieldid = ctrl.id;
this.currentFieldMaxLength = ctrl.maxLength;
this.nextFieldid = ctrlToFocusid;
if ( ( globalKeyCode != TAB_KEY ) && ( globalKeyCode != BACK_TAB_KEY ) ) {
document.getElementById( 'btnSubmit' ).focus();
setTimeout( checkFieldLength()', ONE_MILLISECOND );
}
}
function checkFieldLength() {
// http://www.irt.org/articles/js188/index.htm
var x = document.getElementById( this.currentFieldid ).value.length;
var y = currentFieldMaxLength;
if ( document.getElementById( this.currentFieldid ).value.length ==
this.currentFieldMaxLength ) {
document.getElementById( this.nextFieldid ).focus();
document.getElementById( this.nextFieldid ).select();
}
else {
document.getElementById( this.currentFieldid ).focus();
}
}
function getControl( ctrlid ) {
var ctrl = document.getElementById( ctrlid );
if ( ! ctrl ) {
alert( "Fatal JavaScript error: " + ctrlid + " was not found!" );
}
return ctrl
}
function catchGlobalKeyStroke( e ) {
globalKeyCode = e.keyCode;
}
The action still occurs in the fieldAdvance function, but this version substantially improves on the previous version in two ways:
- This version checks to see if the last key presssed was either the Tab or Back Tab combo (shift/tab) and if so doesn't do anything. This lets users tab in and out of previously fields. More in a moment on how the lost key pressed is tracked.
- This version fixes the problem with a brower's intrinsic type-ahead. It turns out that a textbox's internal value property isn't updated until the control loses focus. So, focus is temporarily set to a given control (more on this in a moment, too) on the form and the JavaScript intrinsic setTimeout() function is used to (after a 1-millisecond delay) call the checkFieldLength() function. By setting the focus before calling checkFieldLength() causes the text box's internal value property to be updated so that its length can be checked in checkFieldLength(). If the length has reached its maximum length, focus is thrown to the field specified; otherwise focus stays in the currently focused textbox. This process occurs for each key press.
A few more details
Tracking the last key pressed
The body tag specifies that the catchGlobalKeyStroke() function is called for each keystroke in the HTML document. catchGlobalKeyStroke() saves the keycode value of the last key pressed in the globalKeyCode value.
<body onkeydown="catchGlobalKeyStroke( event );">
fieldAdvance() checks this global value and if the last key pressed was either the Tab or Back/Tab keys, fieldAdvance() doesn't do anything.
setTimeout()
fieldAdvance() uses JavaScript's setTimeout() function to call checkFieldLength after a 1 millisecond delay. setTimeout() is a little like a built-in timer for JavaScript. It works great, but you can't pass arguments to the function is calls. Thus, the need for the currentFieldid, currentFieldMaxLength and nextFieldid as global variables--without them checkFieldLength wouldn't know what controls are in play.
setTimeout( checkFieldLength()', ONE_MILLISECOND );
Temporarily throw focus
fieldAdvance() needs to temporary throw focus to another control for a (very) brief period (if it doesn't do this, the value of the textbox isn't updated in realtime). This control can't be invisible, such as with CSS and display:none (it couldn't receive focus if it was). This control, therefore, needs to be an existing control on the form and generally it should be one that isn't related to textbox input. The function registerTemporaryFocusControl() sets this value--and you'll see how to call it from your server code in moment.
The need to temporarily throw the focus does don't reveal itself perceptably in your program at runtime. This is no flicker or screen flash as focus is tossed around.