reptile7's JavaScript blog
Saturday, March 05, 2011
 
Further Adventures in Form Validation, Part 6
Blog Entry #207

We conclude our analysis of HTML Goodies' "Bring Your Forms to Life With JavaScript" tutorial with two 'lagniappe' topics.

The non-default clauses of the first Validate( ) switch statement

The liveForm form's set of required fields comprises five <input type="text"> fields and a select-one selection list. As noted in Blog Entry #203, the first switch statement of the Validator object's Validate( ) method contains case clauses for dealing with radio buttons, checkboxes, and select-multiple selection lists: how might we incorporate these control types as required fields into the liveForm form? Before we do that, however, we'd better make sure those clauses are OK in the first place...

radio

Here's the radio case as it appears in the Validator.js script:

case 'radio':
    for (var x = 0; x < selector.length; x++) {
        if (selector[x].checked == true) {
            isEmpty = false; } }
    break;


Suppose we add

Your age group: *
Under 25
25-44
45-64
65+

as a radio button-based field to the liveForm form and that each button's underlying input element is equipped with an onblur="Validator.Validate(this);" event handler. We check one of the buttons and then tab to the next field (in practice, browsers vary wildly in their radio button tabbing behavior, but for the moment let's assume we're using a browser that lets us do this), triggering Validator.Validate( ). Fast-forwarding to Validate( )'s first switch statement, selector.type evaluates to radio and the radio case fires - so far, so good.

The radio clause is designed to loop through a set of radio buttons and toggle the isEmpty variable to false if any of them are checked. Accordingly, the loop's selector-based expressions - selector.length and selector[x].checked - expect selector to represent a set (or to use the argot of the DOM, a NodeList) of radio buttons. However, if we pass this to Validate( ), then we are sending to Validate( ) an individual radio button object, for which selector.length will evaluate to undefined; as a result, the loop's x < selector.length condition returns false from the get-go, isEmpty remains at true, and a false negative occurs: Validator.invalid( ) is called and the x.gif image is placed next to the checked radio button.

We can solve this problem by recasting the for loop as:

for (var x = 0; x < selector.form.elements[selector.name].length; x++) {
    if (selector.form.elements[selector.name][x].checked)
        isEmpty = false; }

/* checked is itself a boolean property and thus the '== true' part of the if condition is unnecessary. */

The selector.form.elements[selector.name] reference allows us to access the collection of radio buttons having the same name as the selector radio button. The referencing of radio button sets and individual radio buttons is briefly discussed here in the JavaScript 1.3 Client-Side Reference.

checkbox, select-multiple

Upon feeding this to Validate( ), the checkbox and select-multiple cases both work fine as far as they go, so we'll deal with them together. The checkbox clause is quite a bit simpler than the radio clause as it only applies to one checkbox at a time.

case 'checkbox':
    if (selector.checked) {
        isEmpty = false; }
    break;


The select-multiple clause is almost identical to the radio clause: it loops through a select-multiple selection list's options and toggles isEmpty to false if any of them are selected.

case 'select-multiple':
    for (var x = 0; x < selector.length; x++) {
        if (selector[x].selected == true) { /* Again, the '== true' part of the condition is unnecessary. */
            isEmpty = false; } }
    break;


The client-side Select object does have a length property that [r]eflects the number of options in the selection list, so we don't just skip over the for loop body this time. More interesting is the loop's if condition: the options[ ] collection of the Select object is the standard interface for iteratively going through a set of selection list options, and the selector[x] expression might not seem like a valid way to access those options (at least I've never seen this syntax for referencing Option objects before), but it does do so, so there you have it.

undefined

I suppose I should mention that the radio clause is preceded by a one-line case 'undefined': break; clause. I don't know why the author included this clause in the switch statement (as some sort of error catcher, perhaps?) and it does seem pointless at first glance, but could we make use of such a clause if we wanted to? Sure - in particular, we can use it to 'bring to life' non-control inline elements that don't normally have a type attribute. Consider the span element below:

<span id="spanID" onmouseover="Validator.Validate(this);">Isn't this cool?</span>

We can give this element a custom type attribute set to the string undefined:

document.getElementById("spanID").type = "undefined";

Next, let the undefined switch case toggle isEmpty to false:

case 'undefined': isEmpty = false; break;

Now, mousing over the Isn't this cool? span element text will place the check.gif image next to that text - cool indeed.

Practical implementation

Let's get back to our "Your age group *" set of radio buttons. If we respectively place the radio button labels after the radio buttons (as is standard practice), and if we use the utils.js InsertAfter( ) function to mark our radio button input (we shouldn't, but bear with me for a moment), then the check.gif or x.gif image will be inserted between a button and its label:

Under 25

This will also be true for a corresponding set of checkboxes. Maybe you're comfortable with this: I don't like it.

Another issue arises if we make a radio button selection and then change our selection to another button: check.gif markers will be inserted, and remain, next to both selections, i.e., the check.gif marker next to our initial selection will not be removed.

Under 25
25-44

For a corresponding set of checkboxes, we will at least see x.gif and check.gif markers respectively next to our initial and subsequent selections in this case. These marker location/redundancy issues are not relevant to a select-multiple selection list, which is marked by InsertAfter( ) with a single, baseline-aligned image (the select and option elements have highly restrictive content models and cannot have img element children).

À la a select-multiple selection list, it would IMO be better to mark a uninamed set of radio buttons or checkboxes with only one check.gif/x.gif image; moreover, it would look nicer if that image lined up horizontally with the check.gif/x.gif images that mark the form's other required fields. Drawing inspiration from the "Dummy Form" of HTML Goodies' "How to Populate Fields from New Windows Using JavaScript" tutorial, which we worked through not so long ago, we can do this as follows:

(1) Wrap each radio button/checkbox and its label in a block-level, width:90%; label element.
(2) Precede the first label element with a zeroed-out, right-floated img placeholder that will later be visibilized and filled with a check.gif or x.gif image, which will vertically align with the first radio button/checkbox and its label; this approach can also be used to top-align the image that marks a select-multiple selection list.

label { display: block; width: 90%; }
.imageClass2 { display: none; float: right; }
...
<p>Your age group: *<br />
<img id="ageGroupImage" class="imageClass2" src="" alt="" />
<label><input type="radio" name="ageGroup" value="Under 25" onclick="testField(this);" /> Under 25</label>
<label><input type="radio" name="ageGroup" value="25-44" onclick="testField(this);" /> 25-44</label>
...


For radio buttons and checkboxes, I find that onclick is supported by all of the OS X GUI browsers on my computer whereas onblur is not supported by Safari and Chrome, so we'll use the former for our validation trigger.

At this point we cut loose from the Validator.js/utils.js code and move to the testField( ) function that underpins the demo of the previous entry. Upon tweaking it a bit, my retooled radio for loop (vide supra) can be used to set a true/false validCondition for both radio buttons and checkboxes:

selectorList = selector.form.elements[selector.name];

if (selector.name == "ageGroup" || selector.name == "checkboxName") {
    for (var x = 0; x < selectorList.length; x++) {
        if (selectorList[x].checked) {
            validCondition = true;
            break; }
        else validCondition = false; } }


An analogous conditional can be written for a select-multiple selection list.
• For checkbox and select-multiple selection list fields: If we make a selection and subsequently deselect that selection, then the else clause will toggle validCondition to false and thereby prevent a false positive from occurring.
• For radio button, checkbox, and select-multiple selection list fields: Without the if clause's break statement, the else clause will produce a false negative if we make a choice and it isn't the last-in-source-order choice.

Demo

The demo below sports a modified liveForm form in which the original form's street text field, state selection list, and zip text field have been respectively replaced by the ageGroup radio button field, a software checkbox field, and a tutorials select-multiple selection list (the fieldNumToValidate remains at 6) - for these new fields, feel free to choose and unchoose selections as you see fit/are able. The firstName, lastName, and email text fields are regulated by the same regular expressions that regulated them in the previous entry's demo.

* = required field
Registration form
Your first name: *


Your last name: *


Your email: *


Company:


Your age group: *

What types of computer software do you regularly use? (Check all that apply.) *

I am interested in tutorials on the following topics (select all that apply): *

 
N.B. To prevent the serial checking of radio buttons and checkboxes from running up the fieldNumValidated count, and because Safari and Chrome would not 'remember' the value of a custom isValid property for the radio button and checkbox collections, it was necessary to define radioOK and checkboxOK variables if (!radioOK) radioOK = false; if (!checkboxOK) checkboxOK = false; for respectively keeping track of the selection states of the radio button and checkbox collections as a whole. A common checkValidationStatus( ) function assays selector.isValid, radioOK, and checkboxOK, and relates these expressions to fieldNumValidated. function checkValidationStatus(testExpression) {     if (validCondition && !testExpression) fieldNumValidated++;     else if (!validCondition && testExpression) fieldNumValidated--;     testExpression = validCondition ? true : false;     return testExpression; } if (selector.type == "text" || selector.type == "select-multiple")     selector.isValid = checkValidationStatus(selector.isValid); else if (selector.type == "radio")     radioOK = checkValidationStatus(radioOK); else if (selector.type == "checkbox")     checkboxOK = checkValidationStatus(checkboxOK);
Let me put off my other lagniappe topic, the utils.js ac( ) function, until the next entry.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

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