Twitter: raymondcamden


Address: Lafayette, LA, USA

A look at JavaScript Form Validation

01-27-2012 8,567 views Development, JavaScript 13 Comments

Validating forms with JavaScript has been possible since the very beginning of time... or at least the introduction of LiveScript (the original name before the marketing drones got ahold of it). While it's not particular new or as exciting as Canvas based games, JavaScript form validation is one of the best uses of the language (imo) and something that can dramatically improve your user's experience. I recently had a reader ask how to do JavaScript form validation so I thought it might be nice to write up a simple tutorial. This is not meant to cover every possible way, of course, and will be a very manual approach (no frameworks or plugins!) to the problem. As always, I welcome folks comments and suggestions below.

Before we write a lick of code, let's look at this topic at a high level. JavaScript form validation comes down to four basic steps:

The first thing you must do is prevent your forms from doing their default behavior - submitting. There are a few ways of doing this but our method will focus on simply listening for the submit event for the form. One tricky thing here to watch out for is ensuring our code doesn't have any errors. Yeah, sure, we should always do that, right? Well one thing that may trip you up is that if an error occurs in your code then the browser will go ahead and submit the form as if nothing happened. This is probably a good thing for the web at large, but during development it can be a bit of a pain. See my random tips at the end for a workaround for this.

The next thing is to introspect the form and figure out what needs to be checked for what. So that's part technical ("how do I know what form field X is set to?") and part business ("what fields do I care about?"). Obviously this guide can't cover every detail, but hopefully it will give you enough to go on to build more complex form validation routines.

The third concern will be reporting the error back to the user. Again, there are a couple of options here. You can use the old Alert prompt, which is ugly, but direct, or simply update the DOM with nice error messages. These can be on top of the form, to the side, underneath, or even next to the particular fields that are incorrect. Again, you've got options, but obviously you need to let the user know what in the heck is wrong.

And lastly, you must absolutely 100% no excuses test your form with JavaScript turned off. While the amount of people without JavaScript is pretty minuscule, there are far too many sites that react badly when you submit a form with JavaScript turned off. I think it's perfectly reasonable to require JavaScript and simply return nothing, or a blunt error, to a user with JavaScript turned off, but you want to ensure bad things don't happen. Assume users will not send the right form fields, will send more data then you expect, less data, etc. Your form submission page is the exhaust port of the Death Star. It's dangerous.

Ok, enough blather. Let's begin with a simple example. I've got a form with two simple fields that I will want to validate. Let's look at the code before any JavaScript code is added.

view plain print about
1<!DOCTYPE html>
2<html>
3    
4<head>
5<title>Form Validation</title>
6</head>    
7    
8<body>
9    
10<form action="something.php" method="post">
11    username: <input type="text" name="username" value=""><br/>
12    password: <input type="password" name="password" value=""><br/>
13    <input type="submit" value="Login">
14</form>
15
16</body>    
17</html>

If for some reason you want to run this as is, you can view it here: http://www.raymondcamden.com/demos/2012/jan/27/test1.html.

So - let's begin. I mentioned that our first task is to listen for, and take over, the form submission. We want to prevent the form from doing what it normally does - submit.

view plain print about
1//get a handle to my form
2var myform = document.getElementById("myform");
3
4//listen for submit
5myform.addEventListener("submit", function(e) {

In order for this to work, we had to add an ID to the form. We could get the form object other ways, but IDs are the most direct, simple way:

view plain print about
1<form action="something.php" method="post" id="myform">

Now let's dig into the event handler I started earlier:

view plain print about
1myform.addEventListener("submit", function(e) {
2 console.log("submit");
3
4 var username = document.getElementById("username").value;
5 var password = document.getElementById("password").value;
6 var errors = '';
7
8 if(username == '') errors += 'Please specify a username.\n';
9 if(password == '') errors += 'Please specify a password.\n';
10
11
12 if(errors != '') {
13 alert(errors);
14 e.preventDefault();
15 }
16
17});

For our form, we want to check the values of the username and password field and ensure they have something in them. My first two lines grab the fields and their values using getElementById. Those of you used to jQuery know that the framework makes this call a bit simpler, but as I said in the beginning I'm intentionally ignoring frameworks for now. I also create a simple errors string variable. This will store the text I'll show to the user in case an error.

The next block begins my simple validation. In my case, I'm just comparing both values to empty strings. Not the most complex validation, but it works. (For extra credit, I could have trimmed the space from my inputs. I've been to many forms that let me bypass their validation by just hitting the space bar.) Note how for each "rule" I simply append to the errors string. This then let's me check if the string is blank. If it isn't, it means something went wrong. I use the alert feature to tell the user.

Here's a critical part. I want to ensure the form does not proceed as normal. Without this one line, it would: e.preventDefault(). e is the name of the variable that the browser will pass the Event object in. Think of it as the actual form submission event. It's not the form, it's the action of me clicking submit.

If you want to test this, use the URL here. Note that I did NOT bother building a file to accept the form input. something.php doesn't exist. If that confuses people, I'll put in a handler, but for now, expect a 404 when the form submits: http://www.raymondcamden.com/demos/2012/jan/27/test2.html

And here is the complete page:

view plain print about
1<!DOCTYPE html>
2<html>
3    
4<head>
5<title>Form Validation</title>
6<script>
7function init() {
8 console.log("init");
9
10 //get a handle to my form
11 var myform = document.getElementById("myform");
12
13 //listen for submit
14 myform.addEventListener("submit", function(e) {
15 console.log("submit");
16
17 var username = document.getElementById("username").value;
18 var password = document.getElementById("password").value;
19 var errors = '';
20
21 if(username == '') errors += 'Please specify a username.\n';
22 if(password == '') errors += 'Please specify a password.\n';
23
24
25 if(errors != '') {
26 alert(errors);
27 e.preventDefault();
28 }
29
30 });
31}
32</script>
33</head>    
34    
35<body onload="init()">
36    
37<form action="something.php" method="post" id="myform">
38    username: <input type="text" name="username" id="username" value=""><br/>
39    password: <input type="password" name="password" id="password" value=""><br/>
40    <input type="submit" value="Login">
41</form>
42
43</body>    
44</html>

So, that works, but is a bit ugly. Let's get rid of the alert. It smells of 1997. How about inserting some text into the page instead? I added a new div, and relevant style, to my page:

view plain print about
1<style>
2#errorDiv {
3 color: red;
4 font-weight: bold;
5}
6</style>
7...
8
9<div id="errorDiv"></div>

The div is empty for now which means the user won't see it until I actually put something in it. Now instead of creating an alert, I'm going to just inject HTML into the div. I modified my error messages to use BR tags. (In case it wasn't clear, the alert uses line breaks, \n, to create the breaks.)

view plain print about
1if(username == '') errors += 'Please specify a username.<br/>';
2if(password == '') errors += 'Please specify a password.<br/>';

And to inject it, I modify the div's innerHTML property:

view plain print about
1var errorDiv = document.getElementById("errorDiv");
2if(errors != '') {
3 errorDiv.innerHTML=errors;
4 e.preventDefault();
5} else errorDiv.innerHTML='';

So - what's up with the else there? When the user hits the form, it's possible that they will make some type of mistake. When they do, a message is added into the div. When they correct the mistake and hit submit, the form submission will go along as normal. But if the form processor is slow, then the user will continue to see the error message. By clearing it, we remove any doubt in the user's mind that their form is being processed.

You can demo this here: http://www.raymondcamden.com/demos/2012/jan/27/test3.html The full code is below:

view plain print about
1<!DOCTYPE html>
2<html>
3    
4<head>
5<title>Form Validation</title>
6<script>
7function init() {
8 console.log("init");
9
10 //get a handle to my form
11 var myform = document.getElementById("myform");
12
13 //listen for submit
14 myform.addEventListener("submit", function(e) {
15 console.log("submit");
16
17 var username = document.getElementById("username").value;
18 var password = document.getElementById("password").value;
19 var errors = '';
20
21 if(username == '') errors += 'Please specify a username.<br/>';
22 if(password == '') errors += 'Please specify a password.<br/>';
23
24 var errorDiv = document.getElementById("errorDiv");
25 if(errors != '') {
26 errorDiv.innerHTML=errors;
27 e.preventDefault();
28 } else errorDiv.innerHTML='';
29
30 });
31}
32</script>
33<style>
34#errorDiv {
35 color: red;
36 font-weight: bold;
37}
38</style>
39</head>    
40    
41<body onload="init()">
42
43<div id="errorDiv"></div>
44    
45<form action="something.php" method="post" id="myform">
46    username: <input type="text" name="username" id="username" value=""><br/>
47    password: <input type="password" name="password" id="password" value=""><br/>
48    <input type="submit" value="Login">
49</form>
50
51</body>    
52</html>

Alrighty... now that we've got the basics down, it's time to kick it up a notch. Let's begin by looking at a slightly more complex form:

view plain print about
1<form action="something.php" method="post" id="myform">
2 <p>
3    name: <input type="text" name="name" id="name" value="">
4    </p>
5    
6    <p>
7    bio: <textarea name="bio" id="bio"></textarea>
8 </p>
9
10 <p>
11 gender: <select name="gender" id="gender">
12 <option value="">Pick One</option>
13 <option value="female">Female</option>
14 <option value="male">Male</option>
15 </select><br/>
16 </p>
17
18 <p>
19 favorite colors:<br/>
20 <input type="checkbox" name="favcolor" id="favcolor-red" value="red"> <label for="favcolor-red">Red</label><br/>
21 <input type="checkbox" name="favcolor" id="favcolor-blue" value="blue"> <label for="favcolor-blue">Blue</label><br/>
22 <input type="checkbox" name="favcolor" id="favcolor-green" value="green"> <label for="favcolor-green">Green</label><br/>
23 </p>
24
25 <p>
26 favorite food:<br/>
27 <input type="radio" name="favfood" id="favfood-pizza" value="pizza"> <label for="favfood-pizza">Pizza</label><br/>
28 <input type="radio" name="favfood" id="favfood-beer" value="beer"> <label for="favfood-beer">Beer</label><br/>
29 <input type="radio" name="favfood" id="favfood-cookie" value="cookie"> <label for="favfood-cookie">Cookies</label><br/>
30 </p>
31
32    <input type="submit" value="Submit">
33</form>

We've now got a text field, a textarea, a select box, a set of checkboxes, and a radio group. The code we used earlier to grab form values will need to be updated a bit. So for example, we can't just get the value of a set of checkboxes.

In order to simplify things a bit, we're going to upgrade our code a bit to something more modern - the Selectors API. Roughly, this gives us jQuery-style APIs to the DOm and simplifies things for us quite a bit, especially in terms of our radio/checkbox groups. The API comes down to two main method: querySelector and querySelectorAll. I recommend this blog entry for a good introduction: HTML5 selectors API -- It's like a Swiss Army Knife for the DOM. This is - yet again - one more cool aspect of HTML5 that seems to be passed over for demos of Canvas apps and kittens. Sigh. That being said, the support for this is actually rather good. When we talk about support and HTML features, typically the bugaboo is IE. In this case, IE8 and above supports the feature. Good enough for me. Let's look at one change:

view plain print about
1var myform = document.getElementById("myform");
2</code>
3
4<p>
5
6versus:
7
8<p>
9var myform = document.querySelector("#myform");

Not too terribly different, right? And if you are used to jQuery then this is familiar. You aren't saving a lot of keystrokes here, but wait, it gets better. Let's take a look at how we grab some of these form fields:

view plain print about
1var name = document.querySelector("#name").value;
2var bio = document.querySelector("#bio").value;

Ok - not too different. Basically we just get to the DOM via the newer API versus the older one. How about the select? Well, select fields have a selectedIndex. Our business rule will be to simply ensure the first item isn't picked, so all we care about is the index.

view plain print about
1var gender = document.querySelector("#gender").selectedIndex;

Ok, now it's time to get fancy. How do we ensure our users pick at least one from the checkbox group? We could use document.getElementById for all four fields and ensure at least one has their checked property set to true. That's not horrible per se, but it's a lot of typing I would hope we can skip. Our radio group has same validation rule. How can we do this nice and easy?

view plain print about
1var colorcbs = document.querySelectorAll("input[name='favcolor']:checked");
2var foodcbs = document.querySelectorAll("input[name='favfood']:checked");

Let's look at the first one since the second example is the same ignoring the name. Our selector is: input[name='favcolor']:checked. Reading left to right we have: Give me input fields that have an attribute name with the value favcolor, and filter to those that are checked. Or in English - what did I check in the favcolor group? These two calls both return an array of DOM items for any checked field. What's cool is that I can then just check the length of this array. This would also let me support things like, "Pick at least one favorite food but no more than three." Let's take a look now at the validation code:

view plain print about
1if(name == '') errors += 'Enter a name.<br/>';
2//bio not required
3if(gender == 0) errors += 'Select a gender.<br/>'
4if(colorcbs.length == 0) errors += 'Select a favorite color.<br>';
5if(foodcbs.length == 0) errors += 'Select a favorite food.<br>';

Not too difficult, right? You can view the full demo here: http://www.raymondcamden.com/demos/2012/jan/27/test4.html

And here is the complete template:

view plain print about
1<!DOCTYPE html>
2<html>
3    
4<head>
5<title>Form Validation</title>
6<script>
7function init() {
8 console.log("init");
9
10 //get a handle to my form
11 var myform = document.querySelector("#myform");
12
13 //listen for submit
14 myform.addEventListener("submit", function(e) {
15 console.log("submit");
16
17 var errors = '';
18 var errorDiv = document.querySelector("#errorDiv");
19
20 var name = document.querySelector("#name").value;
21 var bio = document.querySelector("#bio").value;
22 var gender = document.querySelector("#gender").selectedIndex;
23
24 var colorcbs = document.querySelectorAll("input[name='favcolor']:checked");
25 var foodcbs = document.querySelectorAll("input[name='favfood']:checked");
26
27 if(name == '') errors += 'Enter a name.<br/>';
28 //bio not required
29 if(gender == 0) errors += 'Select a gender.<br/>'
30 if(colorcbs.length == 0) errors += 'Select a favorite color.<br>';
31 if(foodcbs.length == 0) errors += 'Select a favorite food.<br>';
32
33 if(errors != '') {
34 errorDiv.innerHTML=errors;
35 e.preventDefault();
36 } else errorDiv.innerHTML='';
37
38
39 });
40}
41</script>
42<style>
43#errorDiv {
44 color: red;
45 font-weight: bold;
46}
47</style>
48</head>    
49    
50<body onload="init()">
51
52<div id="errorDiv"></div>
53    
54<form action="something.php" method="post" id="myform">
55 <p>
56    name: <input type="text" name="name" id="name" value="">
57    </p>
58    
59    <p>
60    bio: <textarea name="bio" id="bio"></textarea>
61 </p>
62
63 <p>
64 gender: <select name="gender" id="gender">
65 <option value="">Pick One</option>
66 <option value="female">Female</option>
67 <option value="male">Male</option>
68 </select><br/>
69 </p>
70
71 <p>
72 favorite colors:<br/>
73 <input type="checkbox" name="favcolor" id="favcolor-red" value="red"> <label for="favcolor-red">Red</label><br/>
74 <input type="checkbox" name="favcolor" id="favcolor-blue" value="blue"> <label for="favcolor-blue">Blue</label><br/>
75 <input type="checkbox" name="favcolor" id="favcolor-green" value="green"> <label for="favcolor-green">Green</label><br/>
76 </p>
77
78 <p>
79 favorite food:<br/>
80 <input type="radio" name="favfood" id="favfood-pizza" value="pizza"> <label for="favfood-pizza">Pizza</label><br/>
81 <input type="radio" name="favfood" id="favfood-beer" value="beer"> <label for="favfood-beer">Beer</label><br/>
82 <input type="radio" name="favfood" id="favfood-cookie" value="cookie"> <label for="favfood-cookie">Cookies</label><br/>
83 </p>
84
85    <input type="submit" value="Submit">
86</form>
87
88</body>    
89</html>

So... what do you think? If you've never worked with JavaScript, or are just beginning, does this make sense? Any question?

p.s. So I mentioned above that one of the things you have to watch our for is errors in your submit handler. One way to work around that - at least in Chrome - is to enable Console/Preserve log upon navigation. This will keep the error in your log even after the form goes to the submit page.

13 Comments

  • Commented on 01-27-2012 at 12:25 PM
    We're making a big push at work to start using ValidateThis. I'm sure you're familiar with it, for those who aren't it's a rules-based framework that generates client-side (Javascript) validation code, but also server-side (Coldfusion) code to valdate your incoming data on both sides. And even without a framework of this type, you should be, as Ray said, absolutely testing w/o Javascript.
  • Commented on 01-27-2012 at 1:03 PM
    "Your form submission page is the exhaust port of the Death Star. It's dangerous."

    Ray, you are seriously in the wrong job. That is brilliant !!!
  • Joel Cox #
    Commented on 01-27-2012 at 1:15 PM
    I know you said no frameworks or plugins, but someone's going to mention it sooner or later, so it might as well be me.

    http://bassistance.de/jquery-plugins/jquery-plugin...
  • Commented on 01-27-2012 at 1:43 PM
    Ray Camden using a php page in an example.
    I had to shake my head and blink.

    Ray: You're well aware that console.log locks up IE.
    I think you had a blog post a while back where most of
    the comments left were from people saying that it didn't work in IE,
    despite you telling them that you knew console.log didn't work in IE and that you were just using it as a shortcut.

    JavaScript handles events differently in IE vs other browsers. Or at least did. That's why jQuery is so popular
    because fundamental things in JavaScript are implemented
    differently in differnt browsers. So if jQuery had not
    been written, someone would have had to have written it.

    Alert is not the best user experience.
    It's just as easy to type $('#errorDiv').html(errors);
    Oh wait, I see that you removed the alert statement.
    Why show the wrong way to do something and then the right way?
    People don't have time to read how to do something the wrong way.


    Using onload="init" isn't a best practice.
    You don't need to use it at all if you put your js at the end
    of the page instead of the head section.

    name and bio should have label tags.

    The script should be external.

    The css should be external.

    This is my frustration with web development.
    There are so many wrong ways that still work, so
    it's hard to argue against someone that says "Hey if works..."
  • Commented on 01-27-2012 at 4:09 PM
    I like this tutorial, Ray. I think it does a good job of being very accessible to someone just starting out, whlie also nudging them in the direction of easier/better ways to do things.

    As to criticisms that this doesn't show 100% best practices, I say that it's probably ok. I know people who have come a long way in their experience are very much interested in best practices (and so they should be). However, for the person just getting started, best practices don't mean anything if you can't even get the car started. Sometimes having something "just work" is the step in the right direction that a probably-already-very-frustrated developer needs to make progress.

    That won't be a popular opinion, I'm sure, but there it is.
  • Commented on 01-27-2012 at 4:14 PM
    @Joel: Nope, glad you did, and it's my goto plugin for form validation.

    @Dom: Thank you. :)

    @Phillip: I tested my code in IE9 and it worked fine, even with console. I'm guessing though it may not work in 8. In general though I'm happy with telling folks to use the latest.

    Alert - well, I like to "build" my blog entries. Alert isn't wrong. Alert is a quick and simple way to provide feedback. It's not as good as the DOM update one, but it's quicker. I take baby steps when I write code. ;)

    I respectfully disagree with your comments. Not that they are wrong - but that I don't think it's necessary to clutter things now. You say it's wrong. I say - it's not as optimal as it could be. There is absolutely nothing "wrong" with a script block in a page, and with something as short as I did here, I think it is perfectly acceptable.
  • Commented on 01-29-2012 at 6:54 AM
    Hey, nice article.

    I have written a post also about client-side form validation, and also created a very nice piece of code which handles many cases of validation, check it out:

    http://dropthebit.com/150/validation-styling-seman...
  • Commented on 01-29-2012 at 9:44 AM
    Nice - I like the use of data attributes. I'm a huge fan of that feature.
  • Commented on 01-31-2012 at 12:20 PM
    Ray,

    While I like this entry as a whole, I think the introductory part really stands out as something special. It is this sort of high-level thinking that so many technical tutorials (mine included) miss too often.

    So thanks for taking the time to start with a high-level overview before digging into the code.
  • Commented on 01-31-2012 at 1:08 PM
    Thanks Steve.
  • Doug #
    Commented on 01-31-2012 at 1:44 PM
    Is preventDefault() part of vanilla JavaScript? I can't find any documentation regarding it. (But I did find it in <a href="http://api.jquery.com/event.preventDefault/"&...'s docs</a>.)
  • Doug #
    Commented on 01-31-2012 at 1:45 PM
    No htML? Aw maaaaan.
  • Commented on 01-31-2012 at 1:49 PM
    Yep: https://developer.mozilla.org/en/DOM/event.prevent...

    I believe - stress believe - jQuery handles it if your current browser does not.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty