reptile7's JavaScript blog
Saturday, January 22, 2011
Further Adventures in Form Validation, Part 2
Blog Entry #203

We return now to our analysis of HTML Goodies' "Bring Your Forms to Life With JavaScript" tutorial. All of the liveForm form's required fields excepting the email field are validated via a common script pathway, which we'll deconstruct in detail in today's post. In the discussion that follows, we will for now leave to the side the questions "Is this really necessary?" and "Isn't there a much more straightforward way to do this?" and simply state what happens.

A pure-HTML version of the liveForm form appears in "listing 1" on the tutorial's first page; the complete liveForm form plus the rest of the index.html code are reproduced in the first textarea field on the tutorial's third page. The liveForm form's fields can be filled out in any order, so let's begin with the elements[0] firstName field

<p>Your first name: * <br /><input type="text" id="firstName" name="firstName" onblur="Validator.Validate(this);" /></p>

whose label asks us to input a first name. We enter Kris into the firstName field and then tab to the elements[1] lastName field, giving rise to a blur event, which calls the Validate( ) method of the custom Validator object and passes thereto this, an object reference for the firstName field.

Validate( ) is the second method defined for Validator in the Validator.js script file, whose code is reproduced in the textarea field at the end of the tutorial's second page; per its name, Validate( ) will determine whether the firstName field's value is or is not valid. Validate( )'s underlying function is declared to accept two arguments:

Validator.Validate = function(selector, inputType) { ... }

The this object is assigned to the arguments[0] identifier, selector.

The Validate( ) function first assigns the selector object to Validator's currentSelector property, which was declared earlier by Validator's Initialize( ) method. Next, the function calls and passes selector to Validator's preload( ) method.

this.currentSelector = selector;
Validator.preload = function(selector) { ... }

preload( ) is the sixth method defined for Validator in the Validator.js script file. According to the author, preload( ) preloads the valid and invalid images for the current selector when the Validate( ) method is called; this helps the image show up faster when called upon. preload( ) first checks if selector is convertible to true and then reassigns selector to Validator.currentSelector:

if (selector) {
    Validator.currentSelector = selector;

Both of these operations are unnecessary (selector would be undefined, and hence convertible to false, had we forgotten the this parameter in the Validate( ) method call, but it is of course the author's responsibility to not make these kinds of mistakes). preload( ) next sets up validImage and invalidImage properties for Validator.currentSelector that will respectively point to objects representing img elements for the check.gif and x.gif images. Towards this end, preload( ) initially tests if Validator.currentSelector doesn't have validImage and invalidImage properties (note the ! operators in the if condition):

if (!Validator.currentSelector.validImage && !Validator.currentSelector.invalidImage) { ... }

Validator.currentSelector at present has the various standard properties of a text box object (e.g., name, type, value) but we haven't given it any custom properties, and therefore the if condition returns true (also recall that the Initialize( ) method earlier equipped Validator but not Validator.currentSelector with validImage and invalidImage properties). preload( ) subsequently calls the utils.js script file's ce( ) function, which will create from scratch image objects/elements for the check.gif and x.gif images.

The complete utils.js script code should be reproduced in the second textarea field on the tutorial's third page (just before the Conclusion). However, someone at HTML Goodies forgot to escape the < character of the ac( ) function's for loop, and consequently the browser aborts the code at that point, i.e., the < and everything that follows it do not appear in the field; this mistake does not appear in the version of the tutorial (strangely, the below-the-first-textarea-field content of the third page of the version of the tutorial is now gone altogether).

The ce( ) function is called by the following statements:

Validator.currentSelector.validImage = ce('img', {src: Validator.validImage});
Validator.currentSelector.invalidImage = ce('img', {src: Validator.invalidImage});

Here's ce( ) itself:

function ce(e, obj) {
    var a = document.createElement(e);
    for(prop in obj) {
        a[prop] = obj[prop]; }
    return a; }

Each ce( ) function call sends two parameters to ce( ):
(1) the string img, which is given an e identifier, and
(2) a {src: Validator.validImage} or {src: Validator.invalidImage} Object object, which is given an obj identifier; these objects are created via the object initializer syntax and have a single src property respectively set to Validator.validImage and Validator.invalidImage, which were respectively set to img/check.gif and img/x.gif by Validator's Initialize( ) method.

ce( ) first plugs e into a document.createElement( ) command to create an image object/element (more specifically, object implementing the DOM's Element interface), which is given an a identifier. The createElement( ) method goes back to the DOM Level 1 Core; MSIE has supported it since MSIE 4 and I suspect it was Microsoft (vis-à-vis the W3C) that first implemented it.

ce( ) next transfers the obj object's src property, including the value of that property, to the a object via a loop - the loop runs for one iteration in which the prop variable is set to src as a string.

Finally, ce( ) returns the a object to the preload( ) method, which assigns it to the heretofore-undefined validImage or invalidImage property of Validator.currentSelector.

We're not done with preload( ) yet. preload( ) concludes with a conditional

if (Validator.currentSelector.isValid == undefined) {
    Validator.currentSelector.isValid = false; }

that tests if an isValid property has not been set for Validator.currentSelector (it hasn't) and then initializes Validator.currentSelector.isValid to false. When using MSIE 5.2.3, the Validator.currentSelector.isValid == undefined condition throws an 'undefined' is undefined runtime error; changing the condition to !Validator.currentSelector.isValid quashes the error.

Moving back to the Validate( ) function, the preload( ) function call is followed by the declaration of an isEmpty variable, which is initialized to true, and then by two switch statements:

var isEmpty = true;
switch (selector.type) { ... }
switch (inputType) { ... }

We previously encountered the switch statement in HTML Goodies' "A Case for A Case" tutorial, which we deconstructed in Blog Entry #116. The first Validate( ) switch statement applies to all of the liveForm form's required fields whereas the second Validate( ) switch statement applies only to the email field. The first switch statement comprises four case clauses, whose labels are undefined, radio, select-multiple, and checkbox, respectively, and a concluding default clause. The statement's controlling expression is selector.type, which evaluates to text for all of the required text fields (firstName, lastName, email, street, zip) and to select-one for the state selection list, and therefore the statement's default clause is the one that fires.

    if (selector.value) {
        // safari 3.0 = 1024
        // IE 7.0 = 2147483647
        if (selector.maxLength > 0 && !(selector.maxLength == 1024) && !(selector.maxLength == 2147483647)) {
            if (selector.value.length == selector.maxLength) {
                isEmpty = false; } }
        else {
            isEmpty = false; }

(As the liveForm form does not contain radio buttons, a multiple selection list, or checkboxes, it is left to the reader to examine the clauses for these control types. However, we might take up the radio case later, as the reference expressions in this clause are incorrect.)

The default clause comprises an if statement that toggles isEmpty to false if the selector to be validated has not been left blank; the selector.value condition converts to true for all non-empty user inputs. The if statement itself comprises a nested if...else statement whose if clause probes the zip field

<p>Zip code: * <br /> <input type="text" id="zip" name="zip" maxlength="5" onblur="Validator.Validate(this);" /></p>

and whose else clause takes care of the remaining required fields.

The selector.maxLength > 0 && !(selector.maxLength == 1024) && !(selector.maxLength == 2147483647) condition deserves special comment. This condition does not directly flag the zip field but rather excludes the other required fields on the basis of the values of their DOM maxLength properties.

According to the HTML 4.01 Specification, The default value for [the HTML maxlength] attribute is an unlimited number. In practice, maxLength's default value is not at all infinite (although it could be) and is highly browser-dependent: on my computer, maxLength defaults to -1 with Firefox, Opera, Camino, and Netscape 9; to 524288 (219) with Safari 5.0.3 and Chrome; and to 32000 with MSIE 5.2.3.

The selector.maxLength > 0 subcondition is thus meant to weed out the non-zip required text fields for users using browsers for which the default maxLength value is -1, and it does do that. The two preceding // comments indicate that the !(selector.maxLength == 1024) and !(selector.maxLength == 2147483647) subconditions are similarly meant to weed out the non-zip required text fields for users surfing with Safari 3.0 and MSIE 7.0, respectively, but the 1024 and 2147483647 default maxLength values don't mesh with the most recent versions of Safari and MSIE for the Mac, as noted above; as a result, the overall condition returns true for all of the required text fields when using Safari 5.0.3 and MSIE 5.2.3 and also Chrome. Regarding the state selection list, for which selector.maxLength returns undefined, the overall condition returns false regardless of browser.

If we were validating the zip field, then the selector.value.length == selector.maxLength condition of the furthest-nested if statement would return true, and isEmpty would be switched to false, for any five-character input, whether digits or not. For the other required text fields, we aren't supposed to hit this conditional but Safari 5.0.3, Chrome, and MSIE 5.2.3 do hit it because of the maxLength snafu described above.

For Safari 5.0.3/Chrome and MSIE 5.2.3 non-zip required text field inputs that don't respectively comprise 524288 and 32000 characters - a reasonably safe assumption, yes? - the selector.value.length == selector.maxLength condition will return false and isEmpty won't be switched to false; as a result, these inputs will generate 'false negatives': the Validate( ) function will judge them to be invalid (vide infra) and consequently the x.gif image will be placed next to the fields holding them, even though they should be deemed valid by virtue of having passed the selector.value test of the outermost if statement.

We will discuss the second switch statement in a subsequent post - the non-email required field inputs 'pass through' this statement but are not affected by it.

The Validate( ) function concludes with a conditional that calls either the invalid( ) or valid( ) method of the Validator object depending on whether isEmpty is true or false, respectively:

if (isEmpty) this.invalid( ); // The field input is invalid.
else this.valid( ); // The field input is valid.

Let's sum up for the document.forms["liveForm"].firstName.value = "Kris" assignment that we made at the beginning of the post.
• The Kris input is deemed valid - isEmpty is false and Validator.valid( ) will be called - when using Firefox, Opera, Camino, or Netscape 9.
• The Kris input is deemed invalid - isEmpty is true and Validator.invalid( ) will be called - when using Safari 5.0.3, Chrome, or MSIE 5.2.3.

I warned you this would be 'gory', didn't I? And we haven't even placed the check.gif/x.gif image yet - we'll do that next time.


Comments: Post a Comment

<< Home

Powered by Blogger

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