reptile7's JavaScript blog
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:
	if (field.attributes[node].nodeValue != "bgyellow")
		field.attributes[node].nodeValue = "bgyellow";
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; }

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] [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;
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";
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.


Comments: Post a Comment

<< Home

Powered by Blogger

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