reptile7's JavaScript blog
Wednesday, March 31, 2010
 
Strung Out on DOM Intrigue
Blog Entry #175

In our last episode, we worked through the change( ) function of the first of two scripts offered by HTML Goodies' "Pure Magic Form Script" tutorial. To recap, change( ) is an onkeyup-triggered function that acts on a form as follows:
(1) For a given text box in the form, change( ) looks at the value and class values of the box's underlying input element and then applies (or doesn't apply) a background color to the box based on those values.
(2) change( ) activates or disables a reset button depending on the value of a changed variable that keeps track of the form as a whole; the changed value is itself determined by the value and class values of the form's text inputs.

Changes to change( ) one

Before moving on, are there any changes we might make to the first "Pure Magic Form Script" script's change( ) function - can we make it more intuitive, or streamline it at all?

Node to Attr

All of the objects and properties that appear in the first script's change( ) function are part of the DOM. Some of these types originated in classical JavaScript but some of them are W3C-implemented 'homegrown' DOM types that were not previously implemented proprietarily by Netscape or Microsoft. Specifically, the attributes[ ] collection and by extension the attribute node object, the nodeName property, and the nodeValue property first saw the light of day in the DOM Level 1 Core Specification; these types are all part of the DOM's Node interface.

I don't know what resource/specification the script's author, Steve Waring, was working off of in crafting the change( ) function, but looking over the DOM Level 1 Core, I see that the attribute node object also implements an Attr interface having name and value properties for respectively accessing the name and value of an element attribute. It follows that the change( ) function's nodeName tokens can be replaced by name tokens and its nodeValue tokens can be replaced by value tokens, for example:
if (field.defaultValue == field.value)
{
	if (field.attributes[node].value != "bgwhite")
	{
		field.attributes[node].value = "bgwhite";
		changed--;
	}
}
This is admittedly a cosmetic change but it would make the script seem somewhat less esoteric, more familiar, if you get my drift.

From for to if...else if

As detailed in the previous post, the first script's background-color and un/disable effects both rely on back-and-forth toggling between two input element classes, bgwhite and bgyellow: this strikes me as appropriate design given that the primary purpose of the HTML class attribute is to serve as a mechanism for applying CSS to groups of elements. The change( ) function body contains a for loop that searches for the class attribute of a specific text input, field, in a form. But prior to calling change( ), we know that the text inputs in the script's form have class attributes, so is the for loop necessary? No, it isn't.

Moreover, there's no need to use the abstract interfaces of the Core DOM to fish for the class value of an element; the DOM Level 1 HTML Specification instituted a className property for this purpose.

The change( ) function acts on combinations of field value and class values; however, these values are checked individually via a series of nested if statements. In two value/class situations we don't want change( ) to do anything: specifically,
(a) if the field value and defaultValue are the same and the field class is bgwhite, or
(b) if the field value and defaultValue are different and the field class is bgyellow,
then there should be no (value-unrelated) changes in the rendering of the form's text inputs and reset button. Consequently, the furthest-nested (nodeValue-testing) if statements do not need else clauses.

Alternatively, if we were to merge those value/class conditionals having a 'parent-child relationship', then we can give the change( ) function a simpler, non-nested structure and make it easier to read. Putting it all together, here's how I would rewrite the first script's change( ) function:
function change(field)
{
	if (field.defaultValue == field.value && field.className == "bgyellow")
	{
		field.className = "bgwhite";
		changed--;
	} 
	else if (field.defaultValue != field.value && field.className == "bgwhite")
	{
		field.className = "bgyellow";
		changed++;
	}
	field.form.reset.disabled = ! changed;
}
And just to be on the safe side, I would also add a .bgwhite { background-color: white; } rule to the style element at the top of the script.

In sum, the change( ) function can be for the most part reduced to an if...else if pair of statement blocks, which can appear in either order. Note that the field.form.reset.disabled = ! changed; assignment must appear outside those blocks in order to peg the activation/disabling of the reset button to the form as a whole (and not to just one text box).

changeall( ) one

In this section, we'll analyze the first script's changeall( ) function, which in combination with the reset button returns the form's controls to their initial states. More specifically, the changeall( ) function redisables the reset button and sets the background colors of both text boxes to white (even if one box already has a white background color). The changeall( ) function does not return the text boxes' values to the boxes' defaultValues: the reset button itself does that.

The changeall( ) function is triggered by a reset event, so we'll need an active reset button to get under way. Starting from scratch, let's say we make a change to the elements[0] box's defaultValue; doing so calls the change( ) function, which dutifully sets the elements[0] box's background color to yellow and activates the reset button. Next, let's click the reset button; the resulting reset event causes the form element's onreset event handler to call the changeall( ) function, which follows the change( ) function in the script's script element.

<script type="text/javascript">
...
function changeall(form) { ... }
...
<form onreset="changeall(this);" action="">
...
<input disabled type="reset" name="reset" /></form>


The changeall( ) function call passes this, an object reference for the form, to the changeall( ) function; the this reference is given the identifier form in the changeall( ) declaration.

The changeall( ) function first disables the reset button by setting the button's disabled property to true, and then sets the changed state variable to 0, signifying that we are returning the form to its original, 'unchanged' state:

form.reset.disabled = true;
changed = 0;


The rest of the changeall( ) function comprises a for loop for setting the text box background colors to white:
for (var el = 0; el < form.elements.length; el++)
{
	if (form.elements[el].name != "reset") /* Some way needed of knowing which to reset. */ 
	{
		for (var node = 0; node < form.elements[el].attributes.length; node++)
		{
			if (form.elements[el].attributes[node].nodeName == "class") 
			{
				form.elements[el].attributes[node].nodeValue = "bgwhite";
				break;
			}
		}
	}
}
I'm not going to go through this code in detail as it mirrors the change( ) function and is thus much more complicated than it needs to be - here are the highlights:

• The loop steps through the form controls via the elements[ ] property of the Form object.

• In each loop iteration, an if statement checks if a control's name is not equal to reset; the form.elements[el].name != "reset" condition returns true for form's text inputs, which are both named ta.

• A nested for loop steps through the attributes[ ] of each text input; when we hit the class attribute, we set it to bgwhite and therefore the box background color to white, regardless of whether the current class value is bgwhite or bgyellow.

For what it seeks to do, the above loop could be replaced by two lines of code:

form.elements[0].className = "bgwhite";
form.elements[1].className = "bgwhite";


On the other hand, there's something to be said for using a loop to run through the form controls (particularly so if we were dealing with a larger number of controls); here's how I would write it:
for (var el = 0; el < form.elements.length; el++)
{
	if (form.elements[el].type != "reset" && form.elements[el].className == "bgyellow")
		form.elements[el].className = "bgwhite";
}

We'll deconstruct the second "Pure Magic Form Script" script in its entirety in the next entry.

reptile7

Monday, March 22, 2010
 
You Light Up My Form Controls
Blog Entry #174

In today's post, we'll take up "Pure Magic Form Script", the fourth in HTML Goodies' "JavaScript Form Scripts" series of tutorials. "Pure Magic Form Script" offers two related scripts that use keyup events to trigger functions that
(a) apply background colors to text boxes,
(b) un/disable a reset button, and
(c) change the text in a div element
upon detecting changes in text box values - pretty magical, huh? Seriously, the "Pure Magic Form Script" scripts do sport an assortment of interesting code - in particular, there's some DOM stuff in the first of these scripts that we've never seen before - so let's put them under the microscope, shall we?

The first "Pure Magic Form Script" script is posted here and demonstrated here; the second "Pure Magic Form Script" script is posted here and demonstrated here. It is common programming practice to use indentation to delineate the nesting of code blocks - something I am habitually guilty of not doing on this blog - the "Pure Magic Form Script" script element code is conventionally indented in the sources of the pm1.html and pm2.html demo pages, but is not indented and accordingly more difficult to read (more specifically, its structure is less intelligible) on the pw1code.html and pm2code.html script pages. I will follow the pm1.html/pm2.html source indentation pattern in my discussion below.

Browser support

In his "Pure Magic Form Script" introduction, Joe warns, The only downfall to the coding is that it only works in MSIE. In Netscape browsers, it'll throw an error, so if you decide to go ahead and use this script, make sure only MSIE browsers are looking at it. Well, that was then and this is now:

(1) All of the DOM code in the first "Pure Magic Form Script" script that was once 'MSIE-specific' - features that were already part of the DOM when Microsoft implemented them in MSIE 5 - now has cross-browser support. I find that this script can be run without incident by all of the OS X GUI browsers on my computer; for that matter, whereas the script is indeed incompatible with the pre-Gecko versions of Netscape, I got it to work with Netscape 6.2.3 in the SheepShaver environment.

(2) Two if declarations in the second "Pure Magic Form Script" script employ the nonstandard Microsoft currentStyle object; as a result, I can run this script with MSIE and Opera but not with Camino, Chrome, Firefox, or Safari. However, we'll see later that only a very minor change is needed to convert the currentStyle code to a cross-browser equivalent.

The form and the div

Both "Pure Magic Form Script" scripts act on a form holding two text boxes and a reset button. Here's the first "Pure Magic Form Script" script's form:

<form onreset="changeall(this);" action="">
<input name="ta" class="bgwhite" size="14" value="Over key me" onkeyup="change(this);" />
<input name="ta" class="bgwhite" size="14" value="or me" onkeyup="change(this);" />
<input disabled type="reset" name="reset" /></form>


The second "Pure Magic Form Script" script's form adds id="t1" and id="t2" attributes to the elements[0] and elements[1] text inputs, respectively, but is otherwise identical.

Note that the reset button is initially disabled via the disabled attribute, a Boolean attribute common to all non-deprecated form control elements - we will undisable the reset button in due course. For referencing purposes, the reset button is given a name="reset" attribute.

The second "Pure Magic Form Script" script additionally acts on a div element that follows the form:

<div id="message"><big>Make your changes to the form</big></div>

The div element holds a text string marked up with a big element, which [r]enders text in a 'large' font, quoting the W3C. I see that HTML 5 is putting the big element out to pasture - not so surprising given that font size now falls under the jurisdiction of CSS.

change( ) one

The first and second "Pure Magic Form Script" scripts may share a common form, but they contain markedly different change( ) and changeall( ) functions triggered by that form - functions whose differing versions nonetheless produce identical background-color and un/disable effects. In this section, we'll analyze the 'first-generation' change( ) function of the first "Pure Magic Form Script" script.

Just above the form on the pm1.html demo page, Joe gives us some instructions: Click on a box and change the text. Watch the reset button and the color change. So, let's begin by clicking on the elements[0] text box - the box whose value (preset text) is Over key me. (You may have noticed that both text inputs are equipped with a name="ta" attribute - as we'll see later, this was done not for referencing purposes but merely to distinguish name-wise the text inputs from the reset button. It's not invalid to give a form's text boxes the same name, but we would of course want to give them different names if we were to ever submit the form to a processing agent.)

With focus given to the elements[0] box, we next change the Over key me text in some way: let's say we append a lowercase a to Over key me. Typing the a generates a keyup event that calls the change( ) function in the script's script element and passes thereto this, an object reference for the elements[0] box. The this reference is given the identifier field in the change( ) declaration:

<script type="text/javascript">
changed = 0;
function change(field) { ... }
...
<input name="ta" class="bgwhite" size="14" value="Over key me" onkeyup="change(this);" />


The change( ) function itself comprises a for loop that steps through the attributes of the field input:

for (var node = 0; node < field.attributes.length; node++) { ... }

We have long been conversant with classical JavaScript's HTML element collection properties: images[ ], forms[ ], links[ ], etc. In Level 1 of the DOM, the W3C implemented an analogous attributes[ ] collection that enables associative or ordinal access to an element's attributes. Go here for the attributes[ ] entry in the DOM Level 3 Core Specification.

The loop's counter variable is named node. For each loop iteration, node will serve as an attributes[ ] ordinal index and thus map onto a field attribute. As you may know, an element attribute is a type of "node" in the DOM's tree-like description of a document.

In turn, the for loop body contains a single if statement lacking an else clause:

if (field.attributes[node].nodeName == "class") { ... }

The DOM nodeName property is briefly described here in the DOM Level 3 Core Specification (this link is also good for the related nodeValue property that we'll see in just a moment); as you would expect, nodeName returns an attribute name when applied to an attribute node. For the loop's first iteration (node = 0), field.attributes[node].nodeName returns name and the if condition returns false, but for the loop's second iteration (node = 1), field.attributes[node].nodeName returns class and the if condition returns true.

We next encounter a nested if statement whose condition compares field's current value to its defaultValue (its initial value in this case) to see if they're the same:

if (field.defaultValue == field.value) { ... }

It is possible for the field.defaultValue == field.value condition to return true at this point; for example, we could have begun our trial run by pressing one of the shift keys by itself (not in conjunction with another key), which would give rise to a keyup event and call the change( ) function without changing field's initial value. But we have in fact made a change to that value and therefore the if condition returns false, so let's move to the if statement's else clause:
else
{
	if (field.attributes[node].nodeValue != "bgyellow")
	{
		field.attributes[node].nodeValue = "bgyellow";
		changed++;
	}
}
The else clause holds an if statement whose condition tests if the class attribute's nodeValue, a DOM property that returns an attribute value when applied to an attribute node, is not equal to bgyellow. As shown above, the class attributes of field and its sister text box are both initially set to bgwhite, and therefore the if condition returns true. For a GUI browser, the default background color of a text box should be white, regardless of the background to which a given Web page might be set. The field.attributes[node].nodeValue = "bgyellow"; assignment switches the field class value to bgyellow, which effectively sets a yellow background-color for field via the style element appearing at the top of the script:

<style type="text/css">
input.bgyellow { background-color: yellow; }
</style>


And in practice, the field box is indeed given a yellow background color by all of the OS X GUI browsers on my computer, notwithstanding the W3C's warning that Authors [should] treat...support [for the application of CSS properties to form controls] as experimental.

The changed++; line increments changed from 0, its initial value that was set at the beginning of the script element, to 1. changed is a state variable that can have one of three values:
(1) 0, if no changes have been made to either text box defaultValue;
(2) 1, if a change has been made to the defaultValue of one text box; or
(3) 2, if changes have been made to the defaultValues of both text boxes.

Control now passes to the following lines, which wrap up the if (field.attributes[node].nodeName == "class") { ... } statement:
field.form.reset.disabled = ! changed;
break;
The field.form.reset.disabled = ! changed; statement is executed whether or not a change had been made to the field value at the time the change( ) function was called; effectively, this statement either
(a) undisables the reset button if it's disabled,
(b) disables the reset button if it's not disabled, or
(c) does nothing at all,
depending on the current state of the reset button and the value of changed.

Classical JavaScript did not equip its input element-related client-side objects with a disabled property, but the DOM does. Like the HTML disabled attribute it reflects, the DOM disabled property applies to almost all form control types (it doesn't apply to the deprecated isindex element); it is a Boolean property that takes the values true or false for a control that is disabled or active, respectively. Via classical JavaScript's form property of the Text object (now appearing here in the DOM), the field.form.reset.disabled expression allows us to access the disabled property of the reset button from the field input.

If changed is 1 or 2, then it is convertible to a Boolean true, and therefore ! changed evaluates to false (see the "Logical Operators" section in the Mozilla JavaScript Guide for a description of the ! operator). Per the above discussion, assigning false to field.form.reset.disabled activates the currently disabled reset button in the present case, but it wouldn't have any effect if the reset button were already active, as would be the case for a second run through the change( ) function in which the field value and defaultValue are not equal, e.g., if we append a b to the Over key mea field value, there would be no visible change in the reset button in going from Over key mea to Over key meab.

If changed is 0, then it is convertible to a Boolean false, and therefore ! changed evaluates to true. Assigning true to field.form.reset.disabled would disable the reset button if it were currently active, but it wouldn't have any effect if the reset button were already disabled, as would be the case if we had initially triggered the change( ) function by pressing one of the shift keys by itself (such a change( ) run would not have incremented changed from 0 to 1).

Finally, a break statement terminates the loop and brings change( )'s execution to a close.

As long as the elements[0] value and defaultValue are different, then the elements[0] background color will remain yellow and the reset button will remain active, regardless of what key(s)* we might press - we could type more characters in the elements[0] box, we could clear the box, we could even tab to the elements[1] box, whatever.
(*Just don't press the enter/return key, as this will induce the browser to try to submit the form.)

All of the above also holds for the elements[1] box if we change its defaultValue (or me) in some way:
• The field background color changes from white to yellow.
changed is incremented to 2 or 1, depending on whether or not the elements[0] defaultValue has also been changed.
• The reset button remains active or becomes active, depending on whether or not the elements[0] defaultValue has also been changed.

But let's return to the elements[0] box and consider another important case: what happens if we delete the terminal a from the Over key mea value and revert to the field defaultValue? As for any other key, pressing the delete key generates a keyup event and calls the change( ) function. In this change( ) run, the condition of the aforementioned if (field.defaultValue == field.value) { ... } statement returns true; the statement's full if clause is given below:
if (field.defaultValue == field.value)
{
	if (field.attributes[node].nodeValue != "bgwhite")
	{
		field.attributes[node].nodeValue = "bgwhite";
		changed--;
	}
}
Earlier the statement's else clause switched the field class value from bgwhite to bgyellow, and therefore the field.attributes[node].nodeValue != "bgwhite" condition of the if clause's nested if statement returns true; subsequently:
(1) The field.attributes[node].nodeValue = "bgwhite"; assignment switches the field class value back to bgwhite; although the bgwhite class is not specifically tied to a background-color:white; CSS declaration, in practice the field background color will change from yellow to white.
(2) The changed--; line decrements changed to 1 or 0, depending on whether or not the elements[1] defaultValue has also been changed.
(3) Control passes to the aforediscussed field.form.reset.disabled = ! changed; statement; the reset button remains active if changed is 1 but is redisabled if changed is 0.

That'll do it for the first script's change( ) function. I think that's enough deconstruction for one post - we'll continue our "Pure Magic Form Script" conversation in the next entry.

reptile7

Thursday, March 11, 2010
 
Input Hopscotch
Blog Entry #173

We continue today our run through HTML Goodies' "JavaScript Form Scripts" series of tutorials. In this post we'll take up the third "JavaScript Form Scripts" tutorial, "Jump Focus with Form Elements". "Jump Focus with Form Elements" presents a script whose effect will probably be familiar to you if you've ever entered a phone number or Social Security number into a Web form: given a form containing a series of text fields, the script automatically moves the user from field to field upon entering a specific number of characters into each field. The "Jump Focus with Form Elements" script is posted here and is reproduced in the div below for your convenience:

<!-- Paste the JavaScript code between the HEAD Tags --> 

<head>

<script type="text/javascript">

// This code makes the jump from textbox one to textbox two
function check( )
{
var letters = document.joe.burns.value.length + 1;
if (letters <= 4)
{ document.joe.burns.focus( ); }
else
{ document.joe.tammy.focus( ); }
}

// This code makes the jump from textbox two to text box three
function check2( )
{
var letters2 = document.joe.tammy.value.length + 1;
if (letters2 <= 4)
{ document.joe.tammy.focus( ); }
else
{ document.joe.chloe.focus( ); }
}

// This code makes the jump from textbox three to textbox four
function check3( )
{
var letters3 = document.joe.chloe.value.length + 1;
if (letters3 <= 4)
{ document.joe.chloe.focus( ); }
else
{ document.joe.mardi.focus( ); }
}

// This code makes the jump from textbox four to the submit button
function check4( )
{
var letters4 = document.joe.mardi.value.length + 1;
if (letters4 <= 4)
{ document.joe.mardi.focus( ); }
else
{ document.joe.go.focus( ); }
}
</script>

</head>

<!-- The onLoad in the BODY tag puts focus in the first textbox -->
<body bgcolor="#ffffff" onload="document.joe.burns.focus( );">

<!-- This is the form -->
<form name="joe" action="">
<input type="text" name="burns" size="10" maxlength="4" onkeyup="check( );" /><br />
<input type="text" name="tammy" size="10" maxlength="4" onkeyup="check2( );" /><br />
<input type="text" name="chloe" size="10" maxlength="4" onkeyup="check3( );" /><br />
<input type="text" name="mardi" size="10" maxlength="4" onkeyup="check4( );" /><br />
<input type="submit" value="Click to Send" name="go" />
</form>

The script serves as a showcase for the onkeyup event handler. onkeyup was not an event handler for the text object in classical JavaScript but was picked up by the W3C for HTML 4 and now may be used with most elements. (For another onkeyup application, check out the lowercase-to-uppercase demo in the "Other 'After-the-Fact' Event Handlers" section of Blog Entry #24.)

Joe provides a script demo in the tutorial's "The Effect" subsection - we'll roll out our own demo later.

Overview

Joe applies his code to a form named joe and comprising four text fields plus a submit button; in source order, the text fields and submit button are respectively named burns, tammy, chloe, mardi, and go. The action begins with the body element start-tag

<body bgcolor="#ffffff" onload="document.joe.burns.focus( );">

whose onload attribute initially places focus in the burns field.

<input type="text" name="burns" size="10" maxlength="4" onkeyup="check( );" />

In his demo, Joe prepends a Put in Four Characters: string to the burns field and And Again: strings to the other fields. (This text does not appear in the code on the jumpfocuscode.html page - it's up to you to add your own field labels.) So, we type some characters in the burns field. As we do so, each keyup event calls the check( ) function in the script's script element:

function check( ) {
var letters = document.joe.burns.value.length + 1;
if (letters <= 4) document.joe.burns.focus( );
else document.joe.tammy.focus( ); }


In each run of the check( ) function, the length of the burns value (what we enter into the field) is indirectly compared to 4, the number of characters we want the field to hold; focus remains with the burns field if that length is 3 or less but shifts to the tammy field when the length reaches 4. Contra the discussion in the tutorial's "From Box One to Box Two" section, the check( ) function would be easier to follow if document.joe.burns.value.length were not incremented and the if declaration employed a simple less-than (not a less-than-or-equal-to) comparison, i.e.,

var letters = document.joe.burns.value.length;
if (letters < 4) document.joe.burns.focus( );


Anyway, this pattern repeats itself for the rest of the form. Entering characters into the tammy field calls an analogous check2( ) function that either keeps focus with the tammy field or shifts it to the chloe field depending on the number of tammy characters; entering characters into the chloe field calls a check3( ) function that keeps focus with the chloe field or shifts it to the mardi field depending on the number of chloe characters; and so on.

value validation, take one

The maxlength="4" attribute of each input element prevents the user from entering more than four characters into each joe field. But as the script stands, there's nothing stopping the user from entering fewer than four characters into a given field and then moving to another field via the mouse cursor or the tab key. Is there some way we can require the user to enter four characters into each field? In theory, this can be straightforwardly accomplished via a function expression of the following form:

document.joe.burns.onblur = function( ) {
if (document.joe.burns.value.length < 4) {
window.alert("Four characters, please.");
document.joe.burns.focus( ); } }


The above code is designed to
(a) notify the user via a Four characters, please alert( ) message and then
(b) return focus to the burns field
if the user deliberately blurs the burns field when its value.length is less than 4. In practice, here's how these expressions (four total, one for each field) play out with the various OS X GUI browsers on my computer:

• With Opera, they work very nicely if they are respectively nested in the corresponding onkeyup-triggered functions, but they gave rise to infinite alert( )-blur loops when I tested them in a separate script element following the joe form.

• With Safari and Chrome, blurring a value.length<4 field usually generates two alert( ) messages but focus is subsequently returned to the field, regardless of where the expressions are placed.

• With Firefox and Camino, blurring a value.length<4 field generates one alert( ) message but focus is not returned to the field, regardless of where the expressions are placed.

• An attempt to sort out the Safari/Chrome/Firefox/Camino behavior by using the window.setTimeout( ) method to delay the execution of the expressions' focus( ) commands also gave rise to infinite alert( )-blur loops.

• I was originally going to leave MSIE out of this discussion - MSIE 5.2.3 for Mac OS X is not what I would call an old browser but it isn't really a modern browser either - but for the record, MSIE gives the desired effect when the expressions are nested in the onkeyup functions and focus( ) command execution is delayed, but behaves problematically under other conditions (I'll spare you the details).

So, validating 'as we go along' is not so straightforward after all. However, we can at least carry out easily a summary validation at the submit-the-form stage, and we'll do that in the "Demo" section below.

Four into one

In the tutorial's "The Full Code" section, Joe discloses that the check( )/check2( )/check3( )/check4( ) functions were originally a single function:
Here's where I often catch heck from experienced programmers. Much of the code I offer on HTML Goodies is wordy. Before you write to tell me, yes, I know this can be done with a single function. In fact, Jim's code was a single function. The code I am offering here breaks out each function individually.

I only do that because it makes the code simpler and allows a less-experienced programmer to grasp the concept. That's all...
Commenter Matt attempts to fill in the blanks:
Instead of writing methods for each instance of the text inputs, write one parameterized method.

function che***(limit, currentId, nextId) {
var element = document.getElementById(currentId);

if (element.value.length >= limit) {
document.getElementById(nextId).focus( ); } }


Then the onKeyUp event for [the burns input] can be "javascript:che***(4, this.id, 'tammy');" and similar on down the line. This will save you a bunch of coding.
Matt is on the right track, but his code makes a fatal mistake: name and id attribute values cannot be used interchangeably in most situations*, and this is the case here. The joe input elements do not have id attributes; as a result,
(a) this.id, the second che***( ) function call parameter, evaluates to an empty string, and
(b) var element = document.getElementById(currentId);, the first che***( ) statement, will return null (which cannot be used as an object in hierarchy expressions, in case you were wondering) - obviously not what we want.

On the other hand, Matt's che***( ) function can be used verbatim if we
(1) equip the joe input elements with id attributes - say, id="input0", id="input1", etc. - and then
(2) set the nth input element's third che***( ) function call parameter to the n+1th input element's id attribute value, i.e.:

<input id="input0" type="text" name="burns" size="10" maxlength="4" onkeyup="che***(4, this.id, 'input1');" /><br />
<input id="input1" type="text" name="tammy" size="10" maxlength="4" onkeyup="che***(4, this.id, 'input2');" /><br />
<!-- Etc. The name attributes should be left in place, as they are required for <input type="text"> elements. -->


*name/id attribute values can be used interchangeably in old-school associative object referencing - for example, <img id="imageID" name="imageName" src=URI alt=""> can be referenced with document.images["imageID"] or document.images["imageName"] - but to the best of my knowledge, that's as far as their overlap goes.

Another point about Matt's code: the onkeyup attribute values should not be formulated as JavaScript URLs - the javascript: protocol can and should be removed.

We can simplify Matt's che***( ) function via the elements[ ] property of the form object. If each input element feeds to che***( ) its elements[ ] index as arguments[0], its value.length as arguments[1], and its value.length limit as arguments[2], e.g.,

<input type="text" name="burns" size="10" maxlength="4" onkeyup="che***(0, this.value.length, 4);" />

then the che***( ) function can be recast as:

function che***(fieldIndex, fieldLength, limit) {
if (fieldLength >= limit)
document.joe.elements[fieldIndex + 1].focus( ); }


Demo

The div below holds a demo that asks the user to enter a 10-digit phone number into a form comprising three text fields, a submit button, and a reset button. The form will "jump focus" if the user enters three digits, three digits, and four digits into the first field, second field, and third field, respectively; the form will not jump focus if a given field contains one or more non-digit characters, but the user can still move to the next field via the mouse cursor or tab key. The button can be clicked at any time; it will pop up an apposite alert( ) message reflecting whether or not the user has entered a 123-456-7890-format phone number into the form. Feel free to try the demo with any combination/number of digit and non-digit characters.

Please enter your 10-digit phone number:

- -




The jump-focus effect is achieved via a jump( ) function:

function jump(fieldIndex, fieldValue, limit) {
if (fieldValue.length >= limit && /^\d{3,4}$/.test(fieldValue))
document.forms["joe"].elements[fieldIndex + 1].focus( ); }


The regular expression-based /^\d{3,4}$/.test(fieldValue) if subcondition enables the jump-focus effect if a field contains three or four digits, and prevents it otherwise. Go here for a description of the test( ) method of the core JavaScript RegExp object.

The user's phone number is validated at the form submission stage via a digitCheck( ) function:

<form id="joe" onsubmit="return digitCheck( );" onreset="document.forms['joe'].elements[0].focus( );"> ... </form>
<script type="text/javascript">
function digitCheck( ) {
var phonenumber = document.forms["joe"].elements[0].value + document.forms["joe"].elements[1].value + document.forms["joe"].elements[2].value;
if (/^\d{10}$/.test(phonenumber)) {
window.alert("Thank you!"); return false; }
else {
window.alert("Your phone number does not comprise ten digit characters. Get it sorted out, will you?"); return false; } }
</script>


The form's field values are concatenated and the resulting string is given the identifier phonenumber. phonenumber is then compared to a /^\d{10}$/ regular expression signifying a ten-digit character sequence; digitCheck( ) pops up a Thank you! message if they match and a Your phone number does not comprise ten digit characters. Get it sorted out, will you? message if they don't. In both cases digitCheck( ) returns false so as to cancel the form's submission (this is a demo, after all).

We'll see more onkeyup action in "Pure Magic Form Script", the fourth "JavaScript Form Scripts" tutorial, whose scripts we'll dissect in the next entry.

reptile7

Monday, March 01, 2010
 
A Different Kind of Submission
Blog Entry #172

Today we begin a tour of HTML Goodies' "JavaScript Form Scripts" series of tutorials. I may or may not write a separate post for the second tutorial in this series, "Checkboxes: Only Two", which we previously discussed in the "Only two checkboxes, please" section of Blog Entry #47, but we'll definitely hit the rest of them.

In this post, we'll check over the first "JavaScript Form Scripts" tutorial, "Submit The Form Using Enter". In "Submit The Form Using Enter", Joe offers a script designed to submit a form when the user presses the enter/return key (hereafter "the enter key") after filling in a text field. Joe claims that the script's effect is one that you've been requesting for a long time now, which I find a bit odd. When I submit a Web form, I want the Webmaster to make the means of submission as obvious as possible by providing a -type of button - there isn't enough of an intuitive connection between hitting the enter key and submitting a form for my tastes. But maybe that's just me.

Anyway, here's Joe's "Submit The Form Using Enter" code:

<script type="text/javascript">
function send( ) {
document.theform.submit( ); }

</script>



<form name="theform" method="post" action="mailto:jburns@htmlgoodies.com" enctype="text/plain">
<b>Submit your Name:</b>
<input type="text" name="mardi" size="30" maxlength="30" onUnfocus="send( );" />


</form>


Joe provides a demo just prior to the tutorial's "The Code" section. Does the code work? Yes, but only accidentally so, as explained below.

Deconstruction

In brief, here's how the script is supposed to work:

The user types a name - say, Joe - into a
Submit your Name:
text box named mardi, the only control in a form named theform. The user presses the enter key, transferring focus away from the text box. The text box's onUnfocus event handler calls the script element's send( ) function, whose document.theform.submit( ); command submits the user's form data, specifically the mardi=Joe name/value pair, to jburns@htmlgoodies.com via the form element's action attribute.

onUnfocus??

There is no "onUnfocus" event handler, at least as far as JavaScript, HTML, and the DOM are concerned. It's not a classical JavaScript event handler, it doesn't appear in HTML 4.01's set of event handlers, it doesn't crop up in the DOM Level 2 Events Specification's "HTML event types" section, not even Microsoft lists it. It just ain't there, folks.

So, what should happen with the onUnfocus="send( );" attribute? Actually, nothing should happen. In the HTML 4.01 Specification's "Notes on invalid documents" section, the W3C recommends, If a user agent encounters an attribute it does not recognize, it should ignore the entire attribute specification (i.e., the attribute and its value). And yet hitting the enter key after entering a name into the mardi field does generate a submit event, as can be verified via adding an onsubmit="window.alert('Form submission in progress...');" probe to the form element start-tag.

One "Submit The Form Using Enter" commenter remarks, onUnfocus is not supported, you should try onblur="send( );" - brilliant idea though. onBlur (or onChange) would indeed seem to be a logical replacement for "onUnfocus", but if the browser is supposed to ignore foreign attributes and their values, then the code should still work if we were to simply throw out both the onUnfocus attribute and the send( ) function that it purportedly calls, and this proves to be the case!

Moreover, a careful reading of the W3C's definition of the blur event - [t]he blur event occurs when an element loses focus either by the pointing device or by tabbing navigation - indicates that pressing the enter key when a text box has focus shouldn't give rise to a blur event anyway. Sure enough, when I subtracted the theform form element (only the earliest browsers require an input element to have a form element parent), gave focus to the mardi field, and pressed the enter key, no blurring occurred - focus remained with the mardi field.

I find that the 'press the enter key, submit the form' effect can be carried out with type="text" and type="password" input fields but with no other form control types. This effect clearly does not constitute default HTML behavior but does seem to be default browser behavior in that it works with all of the OS X browsers on my computer, at least when the form data is submitted to a CGI processing agent. But should it be default browser behavior? Not really, IMO...

Keydown to submit

For current and future browsers that might not support the "Submit The Form Using Enter" effect, pressing the enter key and form submission can be more directly associated via the script below:

<form id="theform" method="post" action=URI enctype=content-type>
<b>Submit your Name:</b>
<input id="nameField" type="text" name="mardi" size="30" maxlength="30" />
</form>

<script type="text/javascript">
function enterToSubmit(e) {
var thisKey = e ? e.which : (event ? event.keyCode : "");
if (thisKey) {
if (thisKey == 13) // If the user presses the enter key:
document.getElementById("theform").submit( ); }
else window.alert("Sorry, your browser does not support the event object."); }
document.getElementById("nameField").onkeydown = enterToSubmit;
</script>


The above code was inspired by a related script presented in Blog Entry #143. As the user fills in the mardi field, each keydown event calls the enterToSubmit( ) function. When the user presses the enter key, the value of enterToSubmit( )'s thisKey variable is 13, the ASCII code position for a carriage return, and the form is submitted. The var thisKey = e ? e.which : (event ? event.keyCode : ""); conditional enables the script to work with browsers supporting either the Netscape event model or the MSIE event model - see Blog Entry #143 for descriptions of the which and keyCode properties of the event object.

The mailto: blues

We previously encountered a mailto: form in the Script Tips #21-24 Script, which we deconstructed in Blog Entry #60. In the "The form element and its submission" section of that entry, I linked to HTML Goodies' "A New Forms Solution Using Perl or FrontPage" tutorial, which notes that mailto: forms are now obsolete for all practical purposes:
The problem arises because modern browsers, IE6 + and equivalent, no longer support email forms! The effect of this is that the visitor fills out the form and clicks the submit button and their email client program is invoked showing a blank email with the specified address in the "To" field. All information entered on the form appears to have been ignored. Essentially, these browsers are treating the email form as if it was a simple "Mailto" email link (for information about email links, see the HTML Mailto: Primer).
In running Joe's "Submit The Form Using Enter" demo, most* of the OS X browsers on my computer do launch Mail, my email client, which in turn opens a "New Message" window. With Opera, the Mail message area is blank per the above quote. However, with Camino, Chrome, Firefox, and Safari, the mardi=Joe form information is displayed in the Mail message area; if other controls (a set of radio buttons, a selection list, etc.) are added to the theform form, their name/value data is also sent to Mail by these browsers. In all cases the Mail To: field holds the jburns@htmlgoodies.com address.

*MSIE does not launch Mail but instead pops up an A connection failure has occurred error message about a minute after pressing the enter key - the underlying problem in this case may be that MSIE 5.2.3 is not really meant for an Intel Mac but for a PowerPC Mac, but I don't know for sure. Perhaps not so surprisingly, Lynx doesn't launch Mail either; Lynx's behavior vis-à-vis Joe's demo is somewhat strange, and it's not really worth it to go into the details thereof, so we won't.

FWIW: The JavaScript 1.3 Client-Side Reference warns here that the submit( ) method of the form object cannot be used with a mailto: form, but this caveat obviously does not apply to browsers that won't send form data to a mailto: URL in the first place.

CGI it

In the tutorial's "The Form Code" section, Joe says, I have this one set to work as a simple mailto: form, but it can just as easily work by attaching the output to a CGI. Just put the path to the CGI right there after the ACTION, like you normally would. In Blog Entry #60 I also linked to an EarthLink "Using the Mailto Script" page that describes the use of a send-form-data-to-an-email-address CGI script that EarthLink once made available to its subscribers; this link is now dead, but the help.earthlink.net/websupport/member/mailto.html page can still be accessed via the Internet Archive.

The instructions on the "Using the Mailto Script" page are quite easy to follow. I plugged the mardi input element into EarthLink's form code, set the "thank you page" to http://home.earthlink.net/~reptile7jr/tx.html*, and then uploaded the resulting code to my EarthLink server space to try out the "Submit The Form Using Enter" effect with it: worked like a charm with all of the OS X browsers on my computer, without exception. The overall mailto script effect is similar to that of the EarthLink appendto script that I demonstrated at http://home.earthlink.net/~reptile7jr/formtarget.html* for Blog Entry #16.
(*The formtarget.html and tx.html pages are at present not online - sorry 'bout that - I will adapt them for a not-dependent-on-EarthLink form targeting demo for Blog Entry #16 at some point in the future.)

In the EarthLink mailto form code, the form element's action attribute is set to home.earthlink.net/cgi-bin/mailto, which points to a script that can only be run by a page hosted by EarthLink's home.earthlink.net server. However, the aforecited "A New Forms Solution Using Perl or FrontPage" tutorial links to two sites that offer free "FormMail" CGI scripts for those of you wishing to pursue this further.

We'll tackle the third "JavaScript Form Scripts" tutorial, "Jump Focus with Form Elements", in the following entry.

reptile7


Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)