reptile7's JavaScript blog
Thursday, February 10, 2011
 
Further Adventures in Form Validation, Part 4
Blog Entry #205

Before we get started...

In the previous entry, I provided links to Mozilla's pages for several DOM features employed by the utils.js script but negligently didn't say anything about the history of those features, so let me do so now. The parentNode property, the removeChild( ) method, the insertBefore( ) method, and the nextSibling property all go back to the DOM Level 1 Core and are all part of the DOM's Node interface. According to irt.org, support for these features began on the Microsoft side with MSIE 5 and on the Netscape/Mozilla side with Netscape 6, strongly suggesting that they were introduced by the W3C and were not previously implemented proprietarily. Working in the SheepShaver environment, I can confirm that MSIE 5 and Netscape 6 both run the tutorial scripts (upon a bit of code cleanup with the former, as-is with the latter) but MSIE 4.5 and Netscape 4.61 do not.

At this point in our discussion of HTML Goodies' "Bring Your Forms to Life With JavaScript" tutorial, we have effectively validated most of the required fields in the liveForm form. The validations of the firstName text field, the lastName text field, the street text field, the state selection list, and the zip text field are all handled by the default clause of the first switch statement of the Validator object's Validate( ) method, which we dissected in detail two entries ago. In today's post we turn our attention to the remaining elements[2] email field

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

which is custom-validated by a validateEmail( ) method in the Validator.js script - after all, for a "Your email: *" field we really ought to be setting the bar a bit higher than merely checking if the field is blank or not, yes? (OK, we did at least require the zip field to contain five characters, but that's not exactly setting the bar high either.)

We begin by entering an email address - say, kris@krishadlock.com - into the email field. We tab to the elements[3] company field, giving rise to a blur event, which calls Validator's Validate( ) method.

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

Like the other required fields, the email field passes to Validate( ) a this self-reference, which is again renamed selector; unlike the other required fields, the email field feeds to Validate( ) a second parameter, namely the string email, which is given an inputType identifier.

All of Validate( ) except for the concluding if (isEmpty) this.invalid( ); else this.valid( ); conditional applies to the email field. À la the other required fields:
(1) this.currentSelector = selector; associates the email field as a child object with the Validator object.
(2) Validator's preload( ) method is called to create objects/elements for the check.gif and x.gif images and also to define an isValid property for the email field.
(3) The isEmpty variable is initialized to true.
(4) Assuming that we are using a browser for which selector.maxLength defaults to -1 (e.g., Firefox, Opera), isEmpty is toggled to false by the default clause of Validate( )'s first switch statement.

Validate( )'s first switch statement is followed by another switch statement that is specifically meant for the email field:

switch(inputType) {
    case 'email':
        this.validateEmail( );
        return; }


This switch statement comprises a single case clause with an email label. The statement's controlling expression is inputType, which evaluates to email for the email field (vide supra) and to undefined for the other required fields; as a result, the statement's email clause fires for the email field and only for that field. Unlike the first switch statement, the second switch statement doesn't have a default clause, but if it did, then that clause would fire for the other required fields.

A related aside:
The first switch statement's first case clause is case 'undefined': break; - if it were present in the second switch statement, this clause would not fire for the non-email required fields because the undefined label is specified as a string.

As shown above, the email clause calls a validateEmail( ) method and then induces an exit from the Validate( ) method via a return; statement. (Mozilla's return statement page strangely now makes no mention of the use of return to stop the execution of a function, but à la Microsoft's corresponding page.)

Validator.validateEmail = function( ) { ... }

validateEmail( ) is the third method defined for Validator in the Validator.js script; it probes the email field's value and then calls Validator's valid( ) or invalid( ) method if that value is perceived to be valid or invalid, respectively. validateEmail( ) begins with a series of variable declarations:

var str = this.currentSelector.value;
var at = "@";
var dot = ".";
var lat = str.indexOf(at);
var lstr = str.length;
var ldot = str.indexOf(dot);


These variabilizations are followed by a series of if statements meant to flag various types of invalid email addresses. Here's the first of these statements:

if (str.indexOf(at) == -1) {
    this.invalid( );
    return false; }


The str.indexOf(at) == -1 condition flags email addresses lacking an @ separator, more specifically, it runs through the characters of str, to which this.currentSelector.value (the email field value) was assigned above, and returns true if @ (variabilized as at) isn't one of them, in which case the statement calls Validator's invalid( ) method. Subsequently, validateEmail( )'s execution is terminated via a return false; statement, the false part of which is unnecessary (recall that all of this action was triggered by a blur event, which is not cancelable).

The next if statement

if (str.indexOf(at) == -1 || str.indexOf(at) == 0 || str.indexOf(at) == lstr) {
    this.invalid( ); return false; }


sports three subconditions (there's no need to parenthesize these subconditions as the equality operators have a higher precedence than does the logical-OR operator):
(a) str.indexOf(at) == -1 is tested again and necessarily returns false (if this expression returned true for the first if statement, then we won't even reach the second if statement as we will have exited validateEmail( ));
(b) str.indexOf(at) == 0 flags email addresses beginning with an @;
(c) str.indexOf(at) == lstr would flag email addresses ending with an @ had the lstr variable been set to str.length - 1 and not str.length, which indexOf( )-wise is one position beyond the end of str, and thus this subcondition necessarily returns false for all email inputs irrespective of the @ character.

Anyway, if the middle subcondition returns true, then this.invalid( ) is called and validateEmail( ) is exited. (In stringing together || operations in this manner, as long as one of the operands returns true, then the overall expression will return true.)

Analogously, the third if statement

if (str.indexOf(dot) == -1 || str.indexOf(dot) == 0 || str.indexOf(dot) == lstr) {
    this.invalid( ); return false; }


catches email addresses lacking a dot (a period/full stop character) or beginning with a dot, but not ending with a dot.

The fourth if statement catches email addresses containing two or more @ characters - the lat variable represents the location of the at separator:

if (str.indexOf(at, (lat + 1)) != -1) {
    this.invalid( ); return false; }


The fifth if statement catches email addresses with a dot immediately before or after the at separator:

if (str.substring(lat - 1, lat) == dot || str.substring(lat + 1, lat + 2) == dot) {
    this.invalid( ); return false; }


The sixth if statement catches email addresses that don't have a dot after the lat + 1 position:

if (str.indexOf(dot, (lat + 2)) == -1) {
    this.invalid( ); return false; }


A final if statement flags email inputs containing one or more space characters:

if (str.indexOf(" ") != -1) {
    this.invalid( ); return false; }


(The @-preceding local-part of an email address can be in the form of a quoted-string that can circumvent some of these if 'prohibitions' - e.g., "abc@. xyz"@example.net is a valid email address according to the ABNF productions in the IETF's "Internet Message Format" specification - but I have to say that I've never seen this type of email address in real life.)

If all of the above if conditions return false, then the email field's value is deemed to be valid and Validator's valid( ) method is called via a final

this.valid( );

command. You can now see why all those return [false] statements are necessary: without them, this last line will also be executed if any of the if conditions are true, in which case a false positive results, i.e., the check.gif image is placed next to the email field even though the field's value is invalid. Alternatively, formulating validateEmail( )'s conditional code as an if...else if...else cascade

if (str.indexOf(at) == -1) this.invalid( );
else if (str.indexOf(at) == 0 || str.indexOf(at) == lstr - 1) this.invalid( );
else if (str.indexOf(dot) == -1 || str.indexOf(dot) == 0 || str.indexOf(dot) == lstr - 1) this.invalid( );
...
else this.valid( );


would allow us to throw out the return statements. Moreover, there's nothing stopping us from combining the various subconditions to give one giant a || b || c || ... condition (each || operand could be placed on its own line for the sake of readability) for which only one this.invalid( ) call would be needed.

Form submission

If we have correctly filled out all of the required fields in the liveForm form - if Validator's fieldNumValidated property has incremented to 6 and its AllFieldsValidated( ) method returns true - then the following line in Validator's valid( ) method

gebid(this.submitId).disabled = false;

sends to the utils.js gebid( ) function this.submitId, which was set to submit, the id of the form's submit button, by Validator's Initialize( ) method; gebid( ) then 'gets' the button so that it can be activated by setting its DOM disabled property to false.

The liveForm form is equipped with an

onsubmit="if(!Validator.AllFieldsValidated()) return false;"

event handler that might seem redundant at first glance - if the button has been activated, then we shouldn't need to check in with the AllFieldsValidated( ) method again, right? But there is actually a use for the onsubmit attribute: upon removing it, I find when using MSIE* that entering a value into any of the liveForm form's text fields (including the non-required company field) and hitting the enter/return key causes the form to prematurely submit - we previously encountered this phenomenon in HTML Goodies' "Submit The Form Using Enter" tutorial - a call to AllFieldsValidated( ) and a false return will cancel that submission.

*With every other OS X GUI browser on my computer, the submit-the-form-using-enter effect is suppressed if there's more than one text field in the form, for whatever reason.

We'll wrap up our look at "Bring Your Forms to Life With JavaScript" with some concluding commentary in the following entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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