reptile7's JavaScript blog
Tuesday, February 22, 2011
 
Further Adventures in Form Validation, Part 5
Blog Entry #206

Up to this point I have diplomatically refrained from making any editorial comments about HTML Goodies' "Bring Your Forms to Life With JavaScript" tutorial and its scripts - we'll take the gloves off today.

Over the course of the tutorial, the author waxes enthusiastic about the use of a custom JavaScript object to validate the fields of the liveForm form; for example, at the bottom of the tutorial's first page, he says, [I]t would be useful to have a JavaScript object that allows us to validate all our forms without having to rewrite code every time we create a new form. I suppose I would agree that the liveforms/ package is impressive vis-à-vis the breadth of the features it employs - we sure have slogged through quite a range of code in the last few entries, haven't we? - but if truth be told I am rather underwhelmed by it all. Indeed, there is such a disconnect between the complexity of the tutorial scripts and the simple validations they ultimately carry out that I almost wonder if the author is playing some sort of joke on the WebReference.com/HTML Goodies readership. The whole shebang cries out for an overhaul...

Image visualization/preloading

Let's begin with the check.gif/x.gif images. I see no point in creating new <img src="check.gif"> and <img src="x.gif"> elements every time we blur one of the liveForm form's required fields. We'll be inserting these images come what may, so a much more sensible approach to image visualization would be to (a) hard-code an img placeholder after each required field

<p>Your first name: *<br /><input type="text" id="firstName" name="firstName" onblur="testField(this);" />
<img id="firstNameImage" class="imageClass" src="" alt="" /></p>
<!-- For ease of referencing, we will give each placeholder a selector.name+"Image" id value. -->


(b) initially zero out the placeholders by setting their CSS display properties to none

.imageClass { display: none; }

(c) visibilize each placeholder later by JavaScriptically switching its display value to inline

var selectorImage = document.getElementById(selector.name + "Image");
selectorImage.style.display = "inline";


and finally (d) set each placeholder's src property to check.gif or x.gif, as appropriate.

So much for the utils.js script's ce( ), rc( ), and InsertAfter( ) functions. Nor am I convinced that Validator's preload( ) method actually preloads the check.gif/x.gif images as, again, the currentSelector.validImage and currentSelector.invalidImage objects are created on the fly. I'm not sure these images need to be preloaded as they're only 4 KB each, but if you want to do so just to be on the safe side, then the following statements, to be executed as top-level (unfunctionized) code in the document head, will do the trick:

var checkImage = new Image(19, 19);
checkImage.src = "img/check.gif";
var xImage = new Image(20, 21);
xImage.src = "img/x.gif";


Field validation

Inspection of the Validator.js code prompts a fundamental question - you're thinking it, I'm thinking it too:

"Where are the regular expressions?"

Regular expressions can and should be used to validate the firstName, lastName, email, and zip fields in the liveForm form. We previously discussed the regular expression-based validation of names, email addresses, and ZIP codes in Blog Entries #48, #50, and #49, respectively.

firstName, lastName

For the firstName/lastName fields, we definitely don't need to settle for a selector.value test that merely checks if they are blank or not. For example, you can limit these fields' inputs to alphabetic characters and require them to respectively contain at least one vowel via the validCondition below:

var normalName = /^[a-z]+$/i;
var vowel = /[aeiouy]/i;
var validCondition;
if (selector.name == "firstName" || selector.name == "lastName")
    validCondition = normalName.test(selector.value) && vowel.test(selector.value);


You might want to set the bar lower here - after all, names can contain white space, hyphens, apostrophes, etc. (within reason, they can even legitimately contain digits and symbolic characters) - however, it is reasonable to require the firstName/lastName inputs to contain some (one or more) alphabetic characters, in which case the above validCondition can be simplified to:

validCondition = /[a-z]+/i.test(selector.value);

email

Borrowing from another WebReference.com resource - the "A Feedback Form" page of WebReference.com's "JavaScript Regular Expressions" tutorial - we can duplicate the various tests of Validator's validateEmail( ) method (all eleven of them) with the following four lines of code:

var emailOK = /^\S+@[a-z0-9.-]+\.[a-z]{2,4}$/i;
var emailNotOK = /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/;
if (selector.name == "email")
    validCondition = emailOK.test(selector.value) && !emailNotOK.test(selector.value);


The emailOK pattern has been adapted from the reg2 pattern on the "Feedback Form" page. The original reg2 permits white space in the local-part of an email address and does not accommodate four-letter top-level domains, so I've modified those parts of the pattern; less importantly, I've also subtracted the reg2 clauses that pertain to email addresses with IP addresses (vis-à-vis domain names) after the @ separator and have made a few other cosmetic changes. The also-renamed emailNotOK pattern, which in collaboration with the ! operator is used to exclude several types of invalid email addresses (those with two or more @ characters, those with consecutive 'dots', etc.), was taken verbatim from the "Feedback Form" page.

zip

This is an easy one:

var zc = /^\d{5}$|^\d{5}-\d{4}$/; // Matches a ZIP or ZIP+4 code
if (selector.name == "zip")
    validCondition = zc.test(selector.value);


There's no need at all for those bizarre maxLength-based tests - chuck them out.

street, state

There's far too much variation in street addresses to write a meaningful regular expression for them, so for the street text field and also the state selection list we will stick with the 'don't leave it blank' validation of the original code:

if (selector.name == "street" || selector.name == "state")
    validCondition = selector.value;


An historical note

We first discussed form field validation in Blog Entry #46 in the course of working through HTML Goodies' JavaScript Primers #29. Somewhat like "Bring Your Forms to Life With JavaScript", Primer #29 presents a script that validates "Enter First Name:" and "Enter Zip Code (99999-9999):" text fields. Without getting into the details, Primer #29's "First Name" validation, which tests if the field is blank or not, is equivalent to what the Validator.js script does for the firstName, lastName, street, and state fields but is carried out in a much more straightforward manner, whereas its "Zip Code" validation, which allows the entry of ZIP+4 codes and actually checks if the first five input characters are digits, is distinctly superior to what Validator.js does for the zip field. Take a bow, Andree and Joe.

Form validation and related functionality

For validating the liveForm form as a whole, we'll still need fieldNumValidated and fieldNumToValidate, which can be defined as 'freestanding' variables (OK, I recognize that this effectively makes them properties of the window object):

var fieldNumValidated = 0, fieldNumToValidate = 6;

We'll also still need to equip the to-be-validated selector with an isValid property:

if (!selector.isValid) selector.isValid = false;

We can now condense Validator's valid( ) and AllFieldsValidated( ) methods to an if block:

if (validCondition) {
    selectorImage.src = checkImage.src;
    if (!selector.isValid) fieldNumValidated++;
    selector.isValid = true;
    if (fieldNumValidated == fieldNumToValidate) document.getElementById("submit").disabled = false; }


Validator's invalid( ) method can similarly be replaced by a complementary else clause. Most of this if...else code can be further compacted via the conditional (?:) operator:

selectorImage.src = validCondition ? checkImage.src : xImage.src;
if (validCondition && !selector.isValid) fieldNumValidated++;
else if (!validCondition && selector.isValid) fieldNumValidated--;
selector.isValid = validCondition ? true : false;
document.getElementById("submit").disabled = (fieldNumValidated == fieldNumToValidate) ? false : true;


We can use an onload-triggered function expression to initially (a) disable the submit button and (b) clear the liveForm form:

window.onload = function ( ) {
    document.getElementById("submit").disabled = true;
    document.getElementById("liveForm").reset( ); }


For MSIE users, we can choke off the submit-the-form-using-enter effect that was discussed at the end of the previous post via a separate onsubmit-triggered function expression:

document.getElementById("liveForm").onsubmit = function ( ) {
    if (fieldNumValidated != fieldNumToValidate) {
        window.alert("Your required data is not complete."); return false; }
    else window.alert("Thank you!"); }


Demo

The div below holds a liveForm-validation demo that incorporates the code presented in this entry. For each of the demo text fields, feel free to enter whatever you want, and then blur the field (i.e., click outside the field or tab to the next field).

* = required field
Registration form
Your first name: *


Your last name: *


Your email: *


Company:

Street address: *

State: *


Zip code: *


The demo form's action attribute is set to an empty string and its method attribute has been toggled to get, so you may safely click the button once all of the required fields have been filled out correctly - your data will be submitted to this page and will be appended as a query string to the page's URL.

We're not 100% finished with "Bring Your Forms to Life With JavaScript": in the name of completeness, I would still like to discuss (1) the non-default clauses of the first switch statement of Validator's Validate( ) method and (2) the utils.js ac( ) function, and I'll do so in the following entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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