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

Comments: Post a Comment

<< Home

Powered by Blogger

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