Beautiful Forms with JavaScript

Using JavaScript to fit the pieces together

Use JavaScript to Easily Clean Up Your Site's Forms

A while back, I posted a blog called "The JavaScript Switcharoo" that presented a technique for eliminating labels from form inputs. It was pointed out in the comments that this technique not only threw errors in Internet Explorer (not good), but would also cause usability issues for screen readers and other text-only browsers (also not good). I've since rewritten the script to cooperate in the ever-troublesome IE family of browsers, as well as using accessible XHTML markup.

View the Demo | Download the Source Code

What are we going for, here?

The desired outcome of this script is to 1) create a standard comment form, 2) dynamically display form field descriptions inside each form field using the information stored in corresponding label elements, then write functions to 3) hide the description when the user focuses on a form field, and 4) replace the description inside the form field if the user navigates away without entering a value. Finally, just for good measure, we'll 5) validate the form before submission to make sure the description doesn't get posted as a comment.

Hiding the label elements helps eliminate the need for troublesome floats in CSS or, heaven forbid, the use of tables in your form layout.

Step 1: Write the XHTML Markup

In order to keep our markup accessible, we're going to use <label> tags to hold descriptions of each <input> or <textarea>.

<form id="test_form" action="form-process.php" method="post">
  <div>
    <label for="comment">Enter your comment here</label>
    <textarea name="comment" id="comment" cols="40" rows="10"></textarea>
    <label for="name">Name (required)</label>
    <input type="text" name="name" id="name" />
    <label for="email">Email (required, not shared)</label>
    <input type="text" name="email" id="email" />
    <label for="website">Website (optional)</label>
    <input type="text" name="website" id="website" />
    <input type="submit" id="submit" value="Post a Comment" />
  </div>
</form>

Above, we've got the markup for a very basic comment form. We're asking visitors for their name and email, and giving them the option to post their website address as well.

This isn't a post about usability, but it's worth mentioning the order in which I've arranged the inputs. It might seem odd that I placed the textarea first, but if you think about the way that people write emails or other general notes, it makes sense. Generally, you would write your note, then sign it, right?

See the result of Step 1

Step 2: Clean up the form with JavaScript

Now that our form is ready, we can clean it up with JavaScript. Our first function, named prettyForms(), will find our form, then cycle through its contained elements and reformat them, creating a beautiful set of inputs that can be easily styled with CSS - no tables or tricky floats necessary!

Let's start by making sure the browser supports the function, then grabbing all the label elements contained within the form using a handy method called getElementsByTagName.

function prettyForms(formid) {
  if ( document.getElementById ) {
    var labels = document.forms[formid].getElementsByTagName("label");
  } else {
    return false;
  }
}

We run a for loop to allow us to process each label in turn.

function prettyForms(formid) {
  if ( document.getElementById ) {
    var labels = document.forms[formid].getElementsByTagName("label");
    for ( i = 0; i < labels.length; i++ ) {
      if ( labels[i].htmlFor != '' ) {
        var formInput = document.getElementById(labels[i].htmlFor);
        formInput.label = labels[i];
      }
    }
  } else {
    return false;
  }
}

First, we need to make sure that the label is attached to an input. To do this, we make sure the property htmlFor isn't blank. Then, we create a variable called formInput that will reference the form field to which the label is attached.

We now have access to both the label and the corresponding form field, so we store the label properties in a new property on the form field (formInput.label) for later reference.

With our labels stored in a variable (labels), we're ready to start making changes.

function prettyForms(formid) {
  if ( document.getElementById ) {
    var labels = document.forms[formid].getElementsByTagName("label");
    for ( i = 0; i < labels.length; i++ ) {
      if ( labels[i].htmlFor != '' ) {
        formInput = document.getElementById(labels[i].htmlFor);
        formInput.label = labels[i];
      } else return false;
      if ( formInput.type != 'checkbox' && formInput.type != 'radio' ) {
        try {
          labels[i].currentStyle.display = "none";
        } catch(e) {
          labels[i].style.display = "none";
        }
        formInput.value = labels[i].innerHTML;
      } else return false;
    }
  } else {
    return false;
  }
}

Before we change anything, we want to make sure we're not dealing with a checkbox or radio type input, because hiding the label for such an element would cause confusion for users.

The first change we make to appropriate form fields is to hide the label. We do this by accessing the label element's style. For browser compatibility, we need to use two different properties, currentStyle and style. In order to make sure the user doesn't get any errors, we us a try ... catch statement, which attempts to run the first command, then, upon failure, executes the alternate second command. In this case, we want to set the display property of our label element to "none".

Next, we set the value of the form field to the innerHTML of the label element. This is an important step, as it's what takes our description out of the label and places it into the form field.

To call this function, we add a couple lines of code to make sure the browser won't choke on the script, verify that the form exists on the page, and then runs the function on the selected form(s). And since we want editing this script to be as simple as possible, we're going to create a variable to store the form's id attribute for easy reference.

if ( document.getElementById ) {
  var formId = "test_form";
  if ( document.getElementById(formId) ) {
    prettyForms(formId);
  }
}

See the result of Step 2

Step 3: Remove the text when the user focuses on a form field

To make our form easy to use, we want to avoid forcing our user to delete the description in order to enter information. Fortunately, we can dynamically remove the description when the user navigates to our form field using the onfocus event handler. To do this, we'll write a new function called textFocus().

function textFocus(input) {
  if ( input.value == input.label.innerHTML ) {
    input.value = '';
  } else {
    return false;
  }
}

When this function is called, it checks the value of the field to see if it matches the innerHTML value of the label property we set in Step 2. If they match, we set the value of the field to blank, successfully removing the description to make room for user input.

To make this function useful, we need to call it when the user focuses on a field. As I mentioned before, this is accomplished by accessing the onfocus event. We can tell the field to execute our textFocus() function when the user focuses on it in the function prettyForms() from Step 2 by adding one line of code (shown in bold):

function prettyForms(formid) {
  if ( document.getElementById ) {
    var labels = document.forms[formid].getElementsByTagName("label");
    for ( i = 0; i < labels.length; i++ ) {
      if ( labels[i].htmlFor != '' ) {
        var formInput = $(labels[i].htmlFor);
        formInput.label = labels[i];
        formInput.onfocus = function() { textFocus(this); }
      }

      // The rest of the function has been omitted to save space

}

By assigning the function textFocus(this) to the onfocus property, each affected form field will run this function when it finds itself in focus, and because we used the special variable this, it will pass itself as a value to the function, so only the description for the selected field will disappear.

It may be worth noting that I didn't attach the function to formInput.onfocus using the following code:

formInput.onfocus = textFocus(this);

When you want a function to fire using event handlers, you have to use a blank function unless the function doesn't have any parameters. A blank function looks like this:

var myVar = function() { Code to be executed }

The reason for this is that if you directly assign the function to a variable, you are assigning the return value of that function, not the function itself. If your function has no parameters, you must assign it without the trailing parentheses (i.e. myVar = myFunction;, not myVar = myFunction();).

See the result of Step 3

Step 4: Replace the description if nothing was entered

If our user selects a field and navigates away without entering information, that field is currently left blank, which could be confusing. To avoid this usability issue, we'll write a function to place the description back in the form field if it's left blank, called textBlur(), and attach it to the field's onblur event.

function textBlur(input) {
  if ( input.value == '' ) {
    input.value = input.label.innerHTML;
  } else {
    return false;
  }
}

This function is the exact opposite of textFocus(); if the value of the field is empty, we set it to the innerHTML value of the label property, effectively putting the description back into the field.

We attach this function to each field's onblur event in prettyForms(), like so:

function prettyForms(formid) {
  if ( document.getElementById ) {
    var labels = document.forms[formid].getElementsByTagName("label");
    for ( i = 0; i < labels.length; i++ ) {
      if ( labels[i].htmlFor != '' ) {
        var formInput = document.getElementById(labels[i].htmlFor);
        formInput.label = labels[i];
        formInput.onfocus = function() { textFocus(this); }
        formInput.onblur = function() { textBlur(this); }
      }

      // The rest of the function has been omitted to save space

}

See the result of Step 4

Step 5: Validate the form before submission

To round out this family of functions, we'll write a script to validate the input. I'm not going to go into verifying that the email address is valid in the interest of keeping this tutorial as easy to understand as possible. You'll want to put checks in place in your upload script as well, but this can help decrease the number of incomplete, unusable, and otherwise bunk submissions.

The function we're going to write, called validateForm(), will cycle through an array of form fields and make sure that the field value doesn't match the value of the form's label, and also that it isn't blank. If either condition is present, we show an alert, cancel the submission of the form, and focus on the offending form field.

But what about non-required fields? In order to avoid requiring all form fields for submission, we have to somehow create a list of "required" elements. The most straightforward approach is to just create a list of the fields we want to check and store it in an array (inputArray).

Let's look at the function:

function validateForm(inputArray) {
  var x;
  for ( x in inputArray ) {
    var inputElm = document.getElementById(inputArray[x]);
    if ( (inputElm.value == inputElm.label.innerHTML) || (inputElm.value == '') ) {
      alert('Please fill out all required fields before submitting this form.');
      inputElm.focus();
      return false;
    }
  }
  return true;
}

First, notice the var x; that kicks off the function. This is a really useful tool that allows you to easily cycle through array values using a for loop. Essentially, you're telling the code, "For however many values are in inputArray, do the following action for each."

Inside the loop, we use the value of the array - our form field's id attribute - to locate the field we want to validate. Then, we check if the current value matches that of the stored label value or if it's blank. If so, we let the user know that they need to fill out all required forms, focus on the field they missed, and prevent the form from submitting (return false;).

If the function returns a value, it stops executing, so if a user accidentally tries to submit a form with fifteen required fields, he or she won't get fifteen alerts - just one to let them know they missed the first field.

To add this into our functions, we have to add two lines of code. First, we have to declare our required fields, which we do with our declaration of the form we'll be using:

if ( document.getElementById ) {
  var formId = "test_form";
  var reqFields = new Array('comment','name','email');
  if ( document.getElementById(formId) ) {
    prettyForms(formId);
  }
}

We then call validateForms() from within prettyForms(), calling it with the form's onsubmit event:

function prettyForms(formid) {
  if ( document.getElementById ) {
    document.forms[formId].onsubmit = function() { return validateForm(reqFields); }
    var labels = document.forms[formid].getElementsByTagName("label");
    for ( i = 0; i < labels.length; i++ ) {

    // The rest of the function has been omitted to save space

See the result of Step 5

Final Thoughts

As you can see in the demo, we now have a perfectly accessible, cross-browser compatible, easy-to-style form! Also, I've done a quick bit of CSS to cause the JavaScript to degrade gracefully if a user is viewing the form with JavaScript disabled. Feel free to use this code however you see fit, and please leave your feedback in the comments!

View the Demo | Download the Source Code

Posted Jan 19, 2009 by Jason Lengstorf.
This entry is filed under javascript, tutorial, and forms.

Want more content like this? Subscribe for FREE!

Comments for This Entry

Gravatar2081803:43AM on January 25, 2009

... just use jQuery, sigh.

Gravatarthr04:25AM on January 25, 2009

Something like this: http://totmacher.eu/blur.html with jquery

Gravatarsl10:02AM on January 26, 2009

jason some nice tips but you have some php include errors in your example pages?

GravatarLoren02:34PM on January 26, 2009

More ways to do overlabelling/compact/beautiful forms:

http://www.alistapart.com/articles/makingcompactformsmoreaccessible/

http://scott.sauyet.com/thoughts/archives/2007/03/31/overlabel-with-jquery/

GravatarMurugan02:47PM on January 26, 2009

Steps 2 & 3 are showing errors.
Actual links are here..
http://ennuidesign.com/demo/beautiful_forms/step2.html
http://ennuidesign.com/demo/beautiful_forms/step3.html

Nice Post!!

GravatarMe Myself02:51PM on January 27, 2009

When you have default values in the fields, then you do not know any more what the field is about as the label disappears. Not good?

GravatarJason Lengstorf07:20PM on January 29, 2009

Thanks for all the feedback!

I fixed the links for step 2 & 3, thank you for pointing those out.

@20818: While there's nothing wrong with jquery, I like the exercise of figuring out how code works and why, then creating a solution to solve the problem. It's more about the experience and learning than it is about speed or ease of implementation.

@Me Myself: The goal is to display the label as long as the user isn't actively using the field. The only way a user could really lose the description without actually filling out the field would be to type something by accident, and even then, by deleting it, they'd get the label back.

Thanks again for the comments!

GravatarAnthony07:21PM on August 02, 2009

Jason,
I am trying to make this work on my site and when I try to submit, I get this error:

Parse error: syntax error, unexpected T_SL in /homepages/37/d221555405/htdocs/contactform.php on line 98

Here is line 98 of my code:
$headers = <<
When looking into this error, a lot of suggested fixes were to get rid of the space or white space after the heredoc and I found this:
"this commonly happens when you use heredoc
syntax ("<<<") and have whitespace before or after the closing
identifier, or after the opening identifier."

I really don't know much about php and am not sure what is the closing and opening identifiers.
Any suggestions on how I can fix this error?

GravatarMotorbike code09:39AM on September 27, 2009

I haven't any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us.i found this informative and interesting blog so i think so its very useful and knowledge able.I would like to thank you for the efforts you have made in writing this article. I am hoping the same best work from you in the future as well.In fact your creative writing abilities has inspired me.Really the blogging is spreading its wings rapidly. Your write up is fine example of it


Gravatartom bowers01:42PM on January 16, 2010

this is great nice work. do you have more?

GravatarJason Lengstorf11:19AM on January 19, 2010

@tom bowers:
I have a few other JS entries up on here; try the "JavaScript" tag at the bottom of this article to get other entries on JS.

I've got a couple other entries on my to-do list that deal with JS and jQuery, as well as a new book coming out in the first half of this year about using jQuery with PHP for AJAX apps.

Thanks for reading!

GravatarPHP Form12:36AM on February 27, 2010

Thank you for a very good post. I'm just beginning to work in programming, but already know something. Even in many of the Internet is not such a simple and accessible information on the web form. Although I use a special tutorial resource http://phpforms.net/tutorial/tutorial.html, but the JS I have not found a better presentation.

Gravatarcolon cleanse01:44AM on March 08, 2010

I am hoping the same best work from you in the future as well.

Post a Comment

Want to show your face? Get a gravatar!

ALLOWED TAGS: <tt><strong><em>