Web Analytics Made Easy -
StatCounter Drop-in Form Validation - CodingForum

Announcement

Collapse
No announcement yet.

Drop-in Form Validation

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Drop-in Form Validation

    I got fed up with the usual javascript form validation, using document.getElementById() to validate every <input> individually.

    So I wrote this wonder of a script. Try it here: Drop-in javascript validator. Here's what it does:
    • Drop-in functionality. Just add it to page and it will detect when the form is submitted and run it through the validator.
    • Makes sure all input and textarea tags have a value. It also bends over backwards to validate radio sets.
    • If there's an error, insertError() adds a red ERROR message right before each input/textarea that's invalid and focus()es them. If there's an error for radio elements, it will add the error message before the first radio button in that set.
    • VERY convenient for LARGE forms that would be prohibitive and time-consuming to validate on an individual element basis.
    • Less than 2 kb. This cruncher gets it to 1284 bytes.
    • Works flawlessly on every browser I tested: Mac IE, Opera 8, Firefox, Safari, IE6/Win. That pretty much covers all the browser engines


    Here's what it *doesn't* do
    • Work for pages with multiple <form>s. I'm sure it could be tweaked to only search for inputs/textareas within a certain form. Right now, it just hooks into the first <form> on the page.
    • Test for specific values. For instance, it won't verify that a ZIP Code input is 5 numbers.
    • Cook your breakfast


    Edit: This is old code. Find the latest script farther down

    Code:
    // By Jalenack
    // http://blog.jalenack.com
    
    function verify() {
    	var errors = false;
    	// get rid of past error messages, if any
    	s = document.getElementsByTagName('strong');
    	
    	for (i = 0; i < s.length; i++) {
    		if (s[i].firstChild.nodeValue == " ERROR:")
    			s[i].style.display = "none";
    	}
    	
    	s = document.getElementsByTagName('input');
    	// validate all inputs
    	
    	a = new Array();
    	b = new Array();
    
    	x = 0;
    	y = 0;
    	
    	// loop and extract all the names of radio sets
    	for (i = 0; i < s.length; i++) {
    		// add if its not already there
    		if (s[i].type == "radio") {
    			// add all checked sets to b
    			if (s[i].checked == true) {
    				b[y] = s[i].name;
    				y++;
    			}
    			// add all 
    			if  (a.toString().indexOf(s[i].name)==-1) {
    				a[x] = s[i].name;
    				// add errors now, will be taken away if they have been checked
    				insertError(s[i], s[i].name + 'xox');
    				x++;
    			}
    		}
    		
    		if (!s[i].value && s[i].type != "hidden") {
    			insertError(s[i], false);
    			errors = true;
    		}
    	}
    
    	// loop all the radio sets, and if any aren't checked, create error
    	for (i = 0; i < a.length; i++) {
    		if  (a.toString().indexOf(b[i])==-1) {
    			errors = true;
    		}
    	}
    		
    	s = document.getElementsByTagName('strong');
    	
    	for (i = 0; i < s.length; i++) {
    		for (x = 0; x < b.length; x++) {
    			if (s[i].getAttribute('class') == b[x]+"xox")
    				s[i].style.display = "none";
    		}
    	}
    
    
    	s = document.getElementsByTagName('textarea');
    	//validate all textareas
    	for (i = 0; i < s.length; i++) {
    	
    		if (!s[i].value) {
    			insertError(s[i], false);
    			errors = true;
    		}
    
    	}	
    
    	if (errors) return false;
    }
    
    function insertError(s, classs) {
    	m = document.createElement('strong');
    	m.appendChild(document.createTextNode(" ERROR:"));
    	m.style.color = "#f00";
    	m.style.borderBottom = "1px dotted #f00";
    	if (classs) m.setAttribute('class', classs);
    	s.focus();
    	s.parentNode.insertBefore(m, s);
    	s.parentNode.insertBefore(document.createTextNode(' '), s);
    }
    
    window.onload = function () {
    	document.getElementsByTagName('form')[0].onsubmit = verify;
    }
    Now, I'm aware that it uses a multitude of loops, so I'd like to ask if it's still fast enough on slow computers. I can't test for that with my G5 with tons of RAM. This is a functional first draft, so if there are any ways I can optimize the code or make it better, please let me know. Enjoy!
    Last edited by Jalenack; Jul 29, 2005, 03:56 PM.
    Jalenack.com .:. YWDA Founder .:. Rounded Corners Maker 1.1! .:. My Blog
    The hardest thing about teaching is not knowing the right answers, but knowing the right questions - Elisabeth Klein
    Pretty buttons does not a great website make.

  • #2
    I don't want to steal your thread, but I had the same idea, too. It just never came to my mind to post it here.

    Seems to be a good opportunity to make up for this now:

    Code:
    <html>
    	<head>
    		<style type="text/css">
    		/* style of the error message START */
    		#errorMsg {
    			display:none;
    		}
    		.validationError {
    			position:relative;
    			color:#ff0000;
    			text-decoration:none;
    			font-weight:700;
    		}
    		.validationError:hover, .validationError:focus, .validationError:active {
    			background-color:#F0F0F0;
    		}
    		.validationError span {
    			position:absolute;
    			border:2px solid #900000;
    			background-color:#e0e0e0;
    			color:#000000;
    			padding:0.25em 0.5em;
    			display:none;
    			z-index:2;
    		}
    		.validationError:hover span, .validationError:focus span, .validationError:active span {
    			top:-1.5em;
    			left:1.5em;
    			display:block;
    		}
    		/* style of the error message END */
    		/* style of the form START */
    		form {
    			margin:1em;
    			padding:0em;
    			width:20em;
    			background-color:#0000ff;
    		}
    		fieldset {
    			margin:1em auto;
    			padding:0em 1.5em 0.5em 1.5em;
    			border:2px solid #0000c0;
    			background-color:#f0f0f0;
    		}
    		legend {
    			color:#0000ff;
    			background-color:#f0f0f0;
    			border:3px solid #0000c0;
    			margin-top:0.25em;
    			padding:0.125em 0.25em;
    			font-weight:700;
    		}
    		label {
    			width:5em;
    			display:block;
    			float:left;
    		}
    		input, textarea {
    			width:11em;
    		}
    		.line {
    			padding-top:0.25em;
    		}
    		.line * {
    			vertical-align:top;
    		}
    		.radio, .checkbox {
    			width:1em;
    		}
    		.button {
    			text-align:center;
    		}
    		.button input {
    			width:4em;
    		}
    		/* style of the form END */
    		</style>
    		<script type="text/javascript">
    		/* these may be changed
    			skip:
    				the specified form elements are not checked for validity
    				form elements are specified as values of the array in the format FORM_NAME.FORM_ELEMENT_NAME
    			callBack:
    				calls a user-defined function for the specified form elements
    				form elements are specified as keys of the array in the format FORM_NAME.FORM_ELEMENT_NAME, the associated functions are the corresponding values
    				the user-defined function must return true on valid input and false if the input could not be validated
    			eMsg:
    				if this is set within a user-defined function and noEMsg == false, an error message with the value of eMsg is created instead of a standard error message
    				eMsg is set to '' after the user-defined function executed (if you want to use a user-defined error message you have to set eMsg on every call of the user-defined function)
    			noEMsg:
    				if this is set within a user-defined function to true, no error message is generated (the user-defined function may create a more appropriate error message)
    				noEMsg is set to false after the user-defind function executed
    			eIcon:
    				this is the icon which if hovered over displays the error message
    				must be a valid node
    		*/
    		var skip = [], callBack = [], eMsg = '', eIcon = document.createTextNode('\240E\240'), noEMsg = false;
    		
    		/* these may not be changed */
    		var errors = 0, lastForm = null, radioSkip = [], done = [], showWho = [];
    		function initValidate() {
    			var f, fNum;
    			for(fNum in document.forms) {
    				if(isNode(f = document.forms.item(fNum)) && !done.inArray(f)) {
    					done.push(f);
    					f.onsubmit = new Function('{return validate(document.forms.item("' + fNum + '")); };');
    				}
    			}
    		}
    		function validate(f) {
    			var eNum, e, send = true, o, i = 0;
    			cleanUp();
    			for(eNum in f.elements) {
    				if(isNode(e = f.elements.item(eNum)) && !done.inArray(e)) {
    					send = checkElement(f, e) && send;
    					done.push(e);
    				}
    				else {
    					if(isObject(o = f.elements.item(eNum)) && typeof o.length != 'undefined') {
    						for(; i < o.length; i++) {
    							if(isNode(o[i]) && !done.inArray(o[i])) {
    								send = checkElement(f, o[i]) && send;
    								done.push(o[i]);
    							}
    						}
    					}
    				}
    			}
    			lastForm = f;
    			return send;
    		}
    		function checkElement(f, e) {
    			var isOK = true, stdEMsg = ' Error: Invalid input / not filled in ', nodeName, nodes;
    			if(skip.inArray(f.name + '.' + e.name)) return true;
    			if((f.name + '.' + e.name) in callBack) {
    				if(!callBack[f.name + '.' + e.name](e)) {
    					if(!noEMsg) {
    						if(eMsg == '') eMsg = stdEMsg;
    						e.parentNode.insertBefore(createEElement(), e.nextSibling);
    					}
    					noEMsg = false;
    					return false;
    				}
    				return true;
    			}
    			else {
    				nodeName = e.tagName.toUpperCase();
    				if(nodeName == 'TEXTAREA') isOK = (e.value != '');
    				else if(nodeName == 'INPUT') {
    					if(['text', 'file', 'password'].inArray(e.type)) isOK = (e.value != '');
    					else if(e.type == 'radio') {
    						if(radioSkip.inArray(e.name)) return true;
    						radioSkip.push(e.name);
    						isOK = radioIsChecked(e.name);
    						nodes = document.getElementsByName(e.name);
    						e = nodes[nodes.length - 1]
    					}
    				}
    				if(!isOK) {
    					eMsg = stdEMsg;
    					e.parentNode.insertBefore(createEElement(), e.nextSibling);
    					return false;
    				}
    				return true;
    			}
    		}
    		function isObject(object) {
    			return (object != null && typeof object == 'object');
    		}
    		function isNode(element) {
    			return (isObject(element) && typeof element.tagName != 'undefined');
    		}
    		function radioIsChecked(name) {
    			var radios = document.getElementsByName(name), i = 0;
    			for(i = 0; i < radios.length; i++) {
    				if(radios[i].checked) return true;
    			}
    			return false;
    		}
    		function createEElement() {
    			var eNode = document.getElementById('errorMsg').cloneNode(true), eMsgNode = eNode.getElementsByTagName('span')[0];
    			if(typeof document.all == 'undefined' || typeof window.opera != 'undefined') {
    				eNode.onmouseout = null;
    				eNode.onmouseover = null;
    				eNode.onblur = null;
    				eNode.onfocus = null;
    			}
    			eNode.id += errors++;
    			eNode.insertBefore(eIcon.cloneNode(true), eMsgNode);
    			eMsgNode.appendChild(document.createTextNode(eMsg));
    			eMsg = '';
    			return eNode;
    		}
    		// the reason why the next two functions exist, can be described with 2 words: Internet Explorer
    		function hideErrorMsg(eNode, who) {
    			showWho[eNode.id] -= who;
    			if(showWho[eNode.id] == 0) {
    				var eMsgNode = eNode.getElementsByTagName('span')[0];
    				eMsgNode.style.display = 'none';
    			}
    		}
    		function showErrorMsg(eNode, who) {
    			if(typeof showWho[eNode.id] == 'undefined') showWho[eNode.id] = 0;
    			var eMsgNode = eNode.getElementsByTagName('span')[0];
    			eMsgNode.style.display = 'block';
    			showWho[eNode.id] |= who;
    		}
    		function cleanUp() {
    			var i = 0, e;
    			for(; i < errors; i++) {
    				e = document.getElementById('errorMsg' + i);
    				e.parentNode.removeChild(e);
    			}
    			errors = 0;
    			radioSkip = [];
    			done = [];
    			showWho = [];
    		}
    		Array.prototype.inArray = function(val) {
    			for(var x in this) {
    				if(typeof(this[x]) == 'function') continue;
    				if(this[x] == val) return true;
    			}
    			return false;
    		}
    		window.onload = initValidate;
    		
    		/* adjust the script to your needs */
    		
    		// let the user-defined function abc handle the validation of field "a" of form "aForm" and field "c" of form "anotherForm"
    		callBack['aForm.a'] = abc;
    		callBack['anotherForm.c'] = abc;
    		// don't check field "b" of form "aForm" and field "a" of form "anotherForm"
    		skip = ['aForm.b', 'anotherForm.a'];
    		// user-defined function; the form element, which should be checked, is passed to it
    		function abc(e) {
    			// if the user typed a 9 into the field, the input is valid
    			if(e.value == 9) return true;
    			// we disable the standard error message for this field
    			noEMsg = true;
    			// ..and make a customized one
    			eMsg = ' Type "9" into the field ';
    			// .. and insert it right after the field (before the next sibling)
    			e.parentNode.insertBefore(createEElement(), e.nextSibling);
    			// it didn't pass the validation check
    			return false;
    		}
    		</script>
    	</head>
    	<body>
    		<a href="javascript:void(0);" id="errorMsg" class="validationError" onMouseOut="hideErrorMsg(this, 1);" onBlur="hideErrorMsg(this, 2);" onMouseOver="showErrorMsg(this, 1);" onFocus="showErrorMsg(this, 2);"><span></span></a>
    		<form name="aForm">
    			<fieldset>
    				<legend>Form 1</legend>
    				<div class="line">
    					<label for="a"> Input 1: </label>
    					<input type="text" name="a" id="a">
    				</div>
    				<div class="line">
    					<label for="b"> Input 2: </label>
    					<input type="password" name="b" id="b">
    				</div>
    				<div class="line">
    					<label for="c"> Input 3: </label>
    					<textarea name="c" id="c"></textarea>
    				</div>
    				<div class="line">
    					<label for="d"> Input 4: </label>
    					<input type="text" name="d" id="d">
    				</div>
    				<div class="line">
    					<label for="z"> Input 5: </label>
    					<input type="radio" name="z" value="1" id="z" class="radio">
    					<input type="radio" name="z" value="2" class="radio">
    					<input type="radio" name="z" value="3" class="radio">
    				</div>
    				<div class="button"><input type="submit" value="Go!"></div>
    			</fieldset>
    		</form>
    		<form name="anotherForm">
    			<fieldset>
    				<legend>Form 2</legend>
    				<div class="line">
    					<label for="a2"> Input 1: </label>
    					<input type="text" name="a" id="a2">
    				</div>
    				<div class="line">
    					<label for="b2"> Input 2: </label>
    					<input type="password" name="b" id="b2">
    				</div>
    				<div class="line">
    					<label for="c2"> Input 3: </label>
    					<textarea name="c" id="c2"></textarea>
    				</div>
    				<div class="button"><input type="submit" value="Go!"></div>
    			</fieldset>
    		</form>
    	</body>
    </html>
    dumpfi
    Last edited by dumpfi; Aug 1, 2005, 11:26 AM.
    "Failure is not an option. It comes bundled with the software."
    ....../)/)..(\__/).(\(\................../)_/)......
    .....(-.-).(='.'=).(-.-)................(o.O)...../<)
    ....(.).(.)("}_("}(.)(.)...............(.)_(.))¯/.
    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    Little did the bunnies suspect that one of them was a psychotic mass murderer with a 6 ft. axe.

    Comment


    • #3
      dumpfi, wow. Yours is even cooler than mine! I especially like the customizable validation functions. Also, the ability to have multiple forms is sweet. Well done.

      One problem is that it doesn't work in IE/mac. I twiddled with it for awhile and I think I narrowed it down to the use of for(x in y) I know, I know, why bother. I'm doing mine for a site with high possibility of IE/mac users, so I'd like it to work there.

      Anyways, I've tweaked mine a bit. It's now a bit smaller and uses two less loops while achieving the same function (how come no one ever told me about getElementsByName() ?? ). Also got rid of some ugly code for radio validation. And got rid of inline styling and added class="validError" to error messages.

      Code:
      function verify() {
      	var errors = false;
      	// get rid of past error messages, if any
      	s = document.getElementsByTagName('strong');
      	
      	for (i = s.length-1; i >=0; i--) {
      		if (s[i].firstChild.nodeValue == " ERROR:")
      			s[i].parentNode.removeChild(s[i])
      	}
      	
      	s = document.getElementsByTagName('input');
      	// validate all inputs
      	
      	a = new Array();
      	b = new Array();
      
      	x = 0;
      	y = 0;
      	
      	// loop and extract all the names of radio sets
      	for (i = 0; i < s.length; i++) {
      		// add if its not already there
      		if (s[i].type == "radio") {
      			// add all checked sets to b
      			if (s[i].checked == true) {
      				b[y] = s[i].name;
      				y++;
      			}
      			// add all 
      			if  (a.toString().indexOf(s[i].name)==-1) {
      				a[x] = s[i].name;
      				x++;
      			}
      		}
      		
      		if (!s[i].value && s[i].type != "hidden") {
      			insertError(s[i]);
      			errors = true;
      		}
      	}
      
      	// loop all the radio sets, and if any aren't checked, create error
      	for (i = 0; i < a.length; i++) {
      		if  (b.toString().indexOf(a[i])==-1) {
      			insertError(document.getElementsByName(a[i])[0]);
      			errors = true;
      		}
      	}
      		
      	s = document.getElementsByTagName('textarea');
      	//validate all textareas
      	for (i = 0; i < s.length; i++) {
      	
      		if (!s[i].value) {
      			insertError(s[i]);
      			errors = true;
      		}
      
      	}	
      
      	if (errors) return false;
      }
      
      function insertError(s) {
      	m = document.createElement('strong');
      	m.appendChild(document.createTextNode(" ERROR:"));
      	m.setAttribute('class', 'validError');
      	s.focus();
      	s.parentNode.insertBefore(m, s);
      	s.parentNode.insertBefore(document.createTextNode(' '), s);
      }
      
      window.onload = function () {
      	document.getElementsByTagName('form')[0].onsubmit = verify;
      }
      One problem I'm having trouble using removeElement() on old error messages. Right now I'm just hacking my way through by making old ones display: none; ... If I use removeElement, it only gets rid of every other <strong>, which I find very odd. Any ideas?
      Last edited by Jalenack; Jul 29, 2005, 03:54 PM.
      Jalenack.com .:. YWDA Founder .:. Rounded Corners Maker 1.1! .:. My Blog
      The hardest thing about teaching is not knowing the right answers, but knowing the right questions - Elisabeth Klein
      Pretty buttons does not a great website make.

      Comment


      • #4
        use removeChild() method

        But in this case you should loop backward, to keep the correct counting (as removing the element will disturb the length of the collection)

        Try:

        for (i = s.length-1; i >=0; i--) {
        if (s[i].firstChild.nodeValue == " ERROR:")
        s[i].parentNode.removeChild(s[i])
        }

        Anyway, looping backward has another advantage: will speed the loop, as in all the languages comparision with 0 is faster that with any other number (to compare two numbers the processor compare each of them with 0, or if one of the numbers is already 0, half of the operation is saved)
        Last edited by Kor; Jul 29, 2005, 07:59 AM.
        KOR
        Offshore programming
        -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

        Comment


        • #5
          Kor, perfect. I never would have thought of that. Thanks

          ... I've edited the script to include that
          Jalenack.com .:. YWDA Founder .:. Rounded Corners Maker 1.1! .:. My Blog
          The hardest thing about teaching is not knowing the right answers, but knowing the right questions - Elisabeth Klein
          Pretty buttons does not a great website make.

          Comment


          • #6
            I've changed my script a bit. Now it should support IE 6.0, too.

            See my first post for the script.

            dumpfi
            Last edited by dumpfi; Aug 1, 2005, 11:23 AM.
            "Failure is not an option. It comes bundled with the software."
            ....../)/)..(\__/).(\(\................../)_/)......
            .....(-.-).(='.'=).(-.-)................(o.O)...../<)
            ....(.).(.)("}_("}(.)(.)...............(.)_(.))¯/.
            ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
            Little did the bunnies suspect that one of them was a psychotic mass murderer with a 6 ft. axe.

            Comment


            • #7
              I think it's not good to put the error messages beside the control as they mess up the layout. I think it's better if you just put a small error icon beside the control and when the user hovers over it, the error description appears as tooltip. There may be other ways to display errors but the idea here is not to mess up the layout.
              Glenn
              vBulletin Mods That Rock!

              Comment


              • #8
                I've implemented glenngvs suggestion and added a short description to the usage of some variables. The example forms were prettified, too.

                See my first post for the script.

                dumpfi
                "Failure is not an option. It comes bundled with the software."
                ....../)/)..(\__/).(\(\................../)_/)......
                .....(-.-).(='.'=).(-.-)................(o.O)...../<)
                ....(.).(.)("}_("}(.)(.)...............(.)_(.))¯/.
                ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                Little did the bunnies suspect that one of them was a psychotic mass murderer with a 6 ft. axe.

                Comment


                • #9
                  how do these validation functions work if there are some optional textboxes in the form. Is there a way to specify in the form which are required?

                  Comment

                  Working...
                  X