Validating forms
When Javascript entered the world, developers used it for many things, but two most popular were client-side form validation and status bar scrollers. No one does the second one anymore, thank you very much.
But the first one is, in my humble opinion, still the best use of client scripting. With DOM-compliant browsers dominating the fields, developers have enough resources to add client-side validation in the least-intrusive way, without adding any extra markup like onsubmit
, onclick
or onblur
handlers in the HTML itself. All you need is to include one or two external script files.
Technique shown here is based on the following set of rules for HTML form markup:
-
fields are enclosed in
div
-
each drop-down and textbox has their own
label
, placed just before the field -
checkboxes and radio buttons are nested inside of
label
-
labels for mandatory fields have
class="mandat"
-
name of the field that awaits string input starts with s, numeric field’s name starts with n and radio buttons and checkboxes starts with b
-
fields that should not be validated (like buttons or hidden fields) starts with x (or anything else then s, n and b)
Additionally, we will use the class named badinput
to mark the fields that are mandatory but empty or where entered data is not correct (i.e. bad date or alphabetic characters in the numeric field). This class will be applied to the enclosing div
.
Before we start: always do the server-side validation. Always! User can easily turn Javascript off, and bypass all checks you’ve done there.
Here is the form we are going to use, with the all the scripts. Lets now dissect the script, step by step.
Idea
I’ve collected a sample of validation functions for common purpose: is the field empty, is data string, integer or float, are the entered date parts a valid date etc. There are many functions of this type, and in the credits block at the file start you can see who contributed on these.
The script I’m gonna explain is Page.js. Essentially, idea is to attach the validating function to the field itself, and to call it on the blur
event. If user is entering data, as soon as the field loses focus, validation will fire and if it returns false, the badinput class will be applied to the its parent div
. In this case, no message should be shown to the user. Messages should popup only if user submits the form and focus should be returned to the offending field.
Why no messages in the first case? If alert box pops up every time and focus is returned to the field, he will have no means to leave the page but to kill the browser. Therefore, we should just visually mark the offending field but leave the user to his own biding.
Setup form
First, we need to process the form at the page start and apply all necessary properties. Simplest way is this:
window.onload = function () {
FV_SetupForm("forma");
}
Of course, in reality, you rarely have just one function that needs to be executed on page load. There are many way to do that; use AttachEvent script I wrote, or one of many similar on the Net, like this one from Scott Andrew.
FV_SetupForm function can accept the form name or the form object:
if (typeof(vForm) == "string")
oForm = document.forms[vForm];
else
oForm = vForm;
The code will use DOM-compliant object referencing, so we need an exit way for older browsers.
if (!oForm.parentNode) return;
We start by setting up the form object itself: add onsubmit
handler, and setup two flags.
First flag, alertuser, will tell us when to display the error messages to the user. Initially it’s false, meaning that messages will not be displayed while user is entering data. You’ll see later on when it’s turn on.
Second one, submitted
, will prevent multiple form submits - on first submit set it to true, and afterwards just cancel the submit.
oForm.onsubmit = function() {
return FV_FormVal(this);
};
oForm.alertuser = false;
oForm.submitted = false;
OK, that was the form. Now lets deal with elements. Using oForm.elements
array, we will go through all the elements, skiping those that don’t have name property set (like fieldsets) and those that do not start with s, n and b. That’s easy part and I won’t repeat it here. When we pass these checks, we need to apply different validation handlers for different fields.
switch(oTmp.type) {
case "text":
case "select-one":
case "password":
case "textarea":
...
break;
case "checkbox":
...
break;
case "radio":
...
break;
}
As you can see, we have three types of handlers. Something missin’..? Yeah, I don’t have select-multiple
here. That one is a combination of regular select and radio buttons and you can build your own code for it based on those two, depending on what you want to check for. (oTmp
is the pointer to the current element).
Textboxes and drop-downs
My intention is to attach validator only to mandatory fields and leave the rest of the checks for the server-side. Therefore, I need to get the label that has word mandat anywhere in the className
property; I said “anywhere” because we have labels with two classes as well as one special case (see the handling of radio buttons).
First get the parent node of the field (enclosing div
) and then get the collection of all its labels. Then, look for the label that corresponds to that mandatory field (sTmp2 is the name of field):
oLabels = oTmp.parentNode.getElementsByTagName("label");
for (var j=0;j<oLabels.length;j++) {
if (oLabels[j].className.indexOf("mandat") != -1 &&
oLabels[j].htmlFor == sTmp2) {
...
break;
}
}
Since we have found our field, no need to go through any further labels in the div. If you want to attach the validator to all fields of these types (not just mandatory ones), simply omit this code.
The validator function is called valme() (VALidate ME) and it’s rather simple:
oTmp.valme = function() {
var oDiv = this.parentNode;
if (this.type == "text" ||
this.type == "password" ||
this.type == "textarea")
this.value = FV_Trim(this.value);
bOk = FV_FieldVal(this);
if (bOk)
oDiv.className = oDiv.className.replace("badinput", "");
else if (oDiv.className.indexOf("badinput") == -1) {
oDiv.className += " badinput";
}
return bOk;
};
oTmp.onblur = oTmp.valme;
First get the pointer to the div
. Then, remove the white characters from the textboxes, and call the actual validating function passing field pointer. If data entered is bad, add (not assign) the class badinput to the div. If data is fine, then remove that class (nothing wrong if the class name is not found, i.e. user got it OK on first try). For the sake of onsubmit
handler, return the result of the validation.
In the end, assign this validator as onblur
handler.
Checkboxes
These fields are nested inside of the labels, which makes the life easier:
oLabel = oTmp.parentNode;
if (oLabel.className.indexOf("mandat") != -1) {
...
}
Again, find the div, and if the field is checked, remove the badinput class. If not, alert the user if needed, and focus the field. Why is alertuser mode toggled after message display? See later on.
oTmp.valme = function() {
var oDiv = this.parentNode.parentNode;
var bOk = this.checked;
if (bOk)
oDiv.className = oDiv.className.replace("badinput", "");
else if (oDiv.className.indexOf("badinput") == -1) {
oDiv.className += " badinput";
if (this.form.alertuser)
FV_ShowMessage("This checkbox is mandatory.");
this.form.alertuser = false;
this.focus();
}
return bOk;
};
oTmp.onblur = oTmp.valme;
Radio buttons
To check the validity, we need to go through the whole collection and see if any of them is checked. Rest of the function is identical to the checkbox one.
var oRadios = this.form.elements[this.name];
var bOk = false;
for (var r=0;r<oRadios.length;r++) {
if (oRadios[r].checked) {
bOk = true;
break;
}
}
This is the special case mentioned before. Here, the radio group is captioned by the paragraph with the mandat class. Labels for radio buttons has the class mandatr since I don’t want to colour them as well. By adding one letter, script still works and the style rule is not applied.
Field validation
For checkboxes and radio buttons, field validation is included in the validator itself, since they are validated on one or few lines of code. For the text fields, we need more space. FV_FieldVal(oField)
function takes the field as input parameter, get its name and separately check for string and numeric types. For string fields, we simply check if the field is empty in which case false is returned.
bOk = !FV_IsBlank(oField.value);
if (!bOk) {
if (oForm.alertuser) {
FV_ShowMessage(sFldName + " is mandatory information.
Please enter.");
oForm.alertuser = false;
oField.focus();
}
break;
}
This is the default handler in the switch
case. If you need any special handling, just place case branches inside of it, as shown in the code for the check of the e-mail field.
Default handling of the numeric fields is identical, except for the check itself:
bOk = (FV_IsFloat(oField.value) || FV_IsInteger(oField.value));
And error message of course.
There is one interesting case, a bit more complicated then the usual checks: the date fields. No matter which date field we are working on, we need the values of all three. Validation must be called on any of them, and that’s why hidden labels have mandat class along with removed.
case "nDobday": case "nDobmonth": case "nDobyear":
var oDay = oForm.elements["nDobday"];
var oMonth = oForm.elements["nDobmonth"];
var oYear = oForm.elements["nDobyear"];
var sDay = FV_Trim(oDay.value);
var sMonth = FV_Trim(oMonth.value);
var sYear = FV_Trim(oYear.value);
Since the months are drop-down list, we can assume that month is always selected. We need to check the values of day and year, and if any of them is empty we have work to do. If alertuser flag is on, show the message, focus the offending fields and stop further execution. If it’s not, then simply return false as result and stop execution. Second case will mark the fields as bad (incomplete) but will not hinder the user in any way.
if (sDay == "" || sYear == "") {
if (oForm.alertuser) {
bOk = false;
FV_ShowMessage("Please fill-in all date fields.");
oForm.alertuser = false;
(sDay == "") ? oDay.focus() : oYear.focus();
break;
} else {
bOk = false;
break;
}
}
However, if all fields are in, then we call the real validation function and code is similar as those already shown.
Form submit
Last part to review is onsubmit
handler. At the start, check the submitted flag and enable the alertuser mode. Then simply loop through fields and execute validators, where they exist. If any of them returns false, exit loop. As shown before, in that moment error message will be displayed and offending field will be focused. After the message display, turn alertuser mode off, since the submit is canceled and user is again entering data; he may decide to give up the whole thing and leave the page.
if (oForm.submitted)
return false;
oForm.alertuser = true;
...
for (var i=0; i<nLen; i++) {
oTmp = oElems[i];
if (typeof(oTmp.valme) == "undefined") continue;
bOk = oTmp.valme();
if (!bOk) break;
}
If all is fine, set the submitted flag, and disable the submit button, since clicking on it will have no effect anymore.
There is one caveat to this whole already submitted affair: if the form submit fails for any reason, user will have no way to resubmit but to refresh the page and retype it all. Handling this is out of scope of this article, you simply need to have that in mind.