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:

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:

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 Alert 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.

  1. 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.
  2. Add a link to the element to each of these names. linked elements solution.
Creative Commons License Unless otherwise expressly stated, all original material of whatever nature created by Christian Heilmann and included in this web site and any related pages is licensed under the Creative Commons License.