Example - A JavaScript enhanced form
Enhancing forms via JavaScript is a very good idea. Nothing is more frustrating than filling out a form, sending it to the server, waiting for the page to load and getting a message that you forgot to enter this or that field.
It is so much nicer for the user to get an immediate response before sending the data, and for us it means less server traffic.
Forms and JavaScript - powerful, yet deceptive
As a usability enhancement, JavaScript can make a form a lot better, but there are some things we need to be aware of:
- We still need to validate on the server side, to avoid invalid data - sent by users without JavaScript - to reach the backend.
- There is no such thing as a "generic form script". Each form is unique and follows certain validation and flow rules. Accommodating each possibility would result in a bloated and slow script, a better way is to re-use parts of a script library. This means easier maintenance and faster execution.
- Let's try to keep the validation techniques the same for the client side
JavaScript and the backend. While we could use classes for client side
validation, f.e.
<input class="required".../>
, they won't be sent when the form gets submitted.
Our form markup
<form action="formsend.php" method="post" onsubmit="return checkform(this);"> <p> <input type="hidden" name="required" id="required" value="name,surname,email,tac,msg,contactform" /> <label for="name">Name</label> <input type="text" name="name" id="name" /><span>*</span> </p> <p> <label for="surname">Surname</label> <input type="text" name="surname" id="surname" /><span>*</span> </p> <p> <label for="email">Email</label> <input type="text" name="email" id="email" /><span>*</span> </p> <p> <label for="phone">Phone number</label> <input type="text" name="phone" id="phone" /> </p> <p> <label for="contactform">Prefered form of contact</label> <select id="contactform" name="contactform"> <option value="">Please choose</option> <option value="p">phone</option> <option value="e">email</option> </select><span>*</span> </p> <p> <label for="msg">Your message</label> <textarea name="msg" id="msg"></textarea><span>*</span> </p> <p> <input type="checkbox" name="tac" id="tac" /> I have read the <label for="tac">terms and conditions</label> and agree with them.</label><span>*</span> </p> <p> <input type="submit" value="Send information" /> </p> </form>
This is a perfectly marked up form, complete with labels to accommodate
non-visual users and those who might have trouble hitting a checkbox with their
pointing device. For validation, we have a hidden field called required
,
that lists all the required fields as a comma separated list. This has been a
standard for form validation scripts (remember formmail.pl
?) for years.
The rules we want to check for are:
- Make sure that each required field was filled in, selected or checked.
- Make sure that the email entered is in a valid format.
Most validation scripts list the required fields that have a problem by name in
a JavaScript alert
. This makes sense if the form is big and complex, but the
alert
is annoying and rather ugly. Let's try another approach for
this small form: Every field that has an error should get a small warning icon
and a red background and we display a
message above the submit button that states that there were some errors.
Our checkform()
script
We start our form script by testing if DOM is available and that there is a
field with the ID required
. If neither is the case, we return
to the document and the PHP fallback script formsend.php
will take
care of the rest.
function checkform(of) { if(!document.getElementById || !document.createTextNode){return;} if(!document.getElementById('required')){return;}
We continue by defining all the variables used in the error display and
by splitting the required field IDs into an array
.
var errorID='errormsg'; var errorClass='error' var errorMsg='Please enter or change the fields marked with a '; var errorImg='img/alert.gif'; var errorAlt='Error'; var errorTitle='This field has an error!'; var reqfields=document.getElementById('required').value.split(',');
As we will add an element with the ID defined in errorID
and add
images to each field with an error, we need to remove all of those should the
script be executed a second time. Otherwise, we'd end up with several
messages and images.
// Cleanup old mess // if there is an old errormessage field, delete it if(document.getElementById(errorID)) { var em=document.getElementById(errorID); em.parentNode.removeChild(em); } // remove old images and classes from the required fields for(var i=0;i<reqfields.length;i++) { var f=document.getElementById(reqfields[i]); if(!f){continue;} if(f.previousSibling && /img/i.test(f.previousSibling.nodeName)) { f.parentNode.removeChild(f.previousSibling); } f.className=''; }
Now we can do what we have undone. We loop over the required fields and test first if the field exists. If not, we skip one round of the loop. This is purely to avoid error messages, the real form markup should have all required fields.
// loop over required fields for(var i=0;i<reqfields.length;i++) { // check if required field is there var f=document.getElementById(reqfields[i]); if(!f){continue;}
We then check each field according to its type. For textareas and text fields
we need to check the value
, for checkboxes we need to check for the checked
attribute and for select boxes if there is a selectedIndex
defined and
that it is bigger than 0
.
If any of the fields have an error, we send it as an object to the method
cf_adderr()
. A special case is the email field, as this one also
needs to be checked for valid email format. This check is performed by another
method called cf_isEmailAddr()
, using regular expressions.
// test if the required field has an error, // according to its type switch(f.type.toLowerCase()) { case 'text': if(f.value=='' && f.id!='email'){cf_adderr(f)} // email is a special field and needs checking if(f.id=='email' && !cf_isEmailAddr(f.value)){cf_adderr(f)} break; case 'textarea': if(f.value==''){cf_adderr(f)} break; case 'checkbox': if(!f.checked){cf_adderr(f)} break; case 'select-one': if(!f.selectedIndex && f.selectedIndex==0){cf_adderr(f)} break; } }
If any of the tests above trigger an error report, the cf_adderr()
generates the error message (a DIV
with the errorid
as ID).
Therefore we return to the sending process of the form only when this element is
not existant.
return !document.getElementById(errorID);
That is the main function, now we need to concentrate on the methods used, the first one being the one adding the error images and the error message.
/* Tool methods */ function cf_adderr(o) {
We create the image, set its alternative text and title and insert it
before the element. We apply the CSS-class stored in errorClass
to
the element to colour it.
// create image, add to and colourise the error fields var errorIndicator=document.createElement('img'); errorIndicator.alt=errorAlt; errorIndicator.src=errorImg; errorIndicator.title=errorTitle; o.className=errorClass; o.parentNode.insertBefore(errorIndicator,o);
Then we check if there is already an error message and create it if necessary. Once we have created this element, this condition will not be executed again.
// Check if there is no error message if(!document.getElementById(errorID)) { // create errormessage and insert before submit button var em=document.createElement('div'); em.id=errorID; var newp=document.createElement('p'); newp.appendChild(document.createTextNode(errorMsg)) // clone and insert the error image newp.appendChild(errorIndicator.cloneNode(true)); em.appendChild(newp);
We find the submit button (by checking the type
of each input element)
and insert the new message before its parent element (the paragraph the submit button resides in).
// find the submit button for(var i=0;i<of.getElementsByTagName('input').length;i++) { if(/submit/i.test(of.getElementsByTagName('input')[i].type)) { var sb=of.getElementsByTagName('input')[i]; break; } } if(sb) { sb.parentNode.insertBefore(em,sb); } } }
Finally, we need the method to test if the submitted email is in a valid format:
function cf_isEmailAddr(str) { return str.match(/^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/); } }
That's all, check the example page and see for yourself how it works.
Why don't you try it?
Simply download the form HTML and try one of the following tasks. Follow the solution links to see one possible solution. The solutions have the JavaScript inline as a demonstration, not in an extra document where they should be. This was done to help you see the necessary markup in one document rather than two.
- Change the code to display a list of all the fields with problems in errormessage. Grab the text to display from their labels. Named elements solution.
- Add a link to the element to each of these names. linked elements solution.