/* formvalidation.class.js

Clint Fountain - Copyright 2008 - All Rights Reserved.

Legal Notice:
	
	This is a protected work. You may not sell, distribute, or create derivitive work based on this library.
		
	Owners of a license for this software are permitted to use this software on any web site that they personally own. 
	Licencees are not permitted to resell this software in any way, including combining this software into a larger product.
	
	THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  
	EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
	PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
	OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
	TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
	PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
	REPAIR OR CORRECTION.
	
	IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
	WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
	REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
	INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
	OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
	TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
	YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
	PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGES.
	
Purpose: 

	Allows easy addition of form validation rules to any HTML document. Avoids the need to custom write Javascript form validation code.
	

Quick Start Instructions

	Activating basic form validation is a simple two step process. First include the formvalidation.class.js file into your document. Then configure your field settings in a different <script> tag section or in a separate Javascript file.

	1.Load the Form Validation class in the HEAD section of your document:

		<script type='text/javascript' src="formvalidation.class.js"></script>

	2.Configure the field and/or form settings between another <script> tag set inside the HEAD section:

		<script type='text/javascript'>
		FORMVALIDATION.fields['fieldID1'] = {required: true};
		FORMVALIDATION.fields['fieldID2'] = {required: true};
		</script>

	Of course basic form field validation is only the start of the capabilities software. You can literally customize any aspect form validation behavior or event using the more advanced features. See documentation for description of all features.
        
*/

// Declare JSLint globals 
/*global alert, document, window, event */

window.FORMVALIDATION = {

	failclass: "",
	//~ failaction:  undefined,
	formlist: {},
	fields: {},
	failure: undefined, // form failure action
	success: undefined, // form success action
	alertrequired:  true,
	showerrors: true, // display errors in a popup alert
	stoponerror: true, // stop execution of program when an error is detected
	prevclassName: {},
	prevsubmit: {},
	prevkeyup: {},
	prevonload: {},
	checkedfields: {},
	autoattach: true, // set to false to prevent auto attachment to form.onsubmit function.  When false you must manually call window.FORMVALIDATION.checkForm('formid') to trigger validation.
	
	init: function() {
	
		var fieldObj;
		
		function attachOnsubmit() {			
		// attach form validation to onsubmit
			if ((window.FORMVALIDATION.autoattach) && (fieldObj.form.id) && (!window.FORMVALIDATION.formlist[fieldObj.form.id]))
			{
				if (typeof(fieldObj.form.onsubmit) == 'function')
				{					
					window.FORMVALIDATION.prevsubmit[fieldObj.form.id] = fieldObj.form.onsubmit;
					//~ fieldObj.form.onsubmit = function(){window.FORMVALIDATION.prevsubmit[fieldObj.form.id](); return window.FORMVALIDATION.checkForm(fieldObj.form.id); };					
					fieldObj.form.onsubmit = new Function("window.FORMVALIDATION.prevsubmit['" + fieldObj.form.id + "'](); return window.FORMVALIDATION.checkForm('" + fieldObj.form.id + "');  ");
				
				}
				else
				{
					//~ fieldObj.form.onsubmit = function(){return window.FORMVALIDATION.checkForm(fieldObj.form.id); };
					fieldObj.form.onsubmit = new Function("return window.FORMVALIDATION.checkForm('" + fieldObj.form.id + "'); ");
				}
				window.FORMVALIDATION.formlist[fieldObj.form.id] = 1;
			}
		
		}
		
		function attachFormat(){
		// attach format function to onkeyup
			var formatVal = "";
			
			if (window.FORMVALIDATION.fields[fieldObj.id].format)
			{
				// execute cutom function
				if (typeof window.FORMVALIDATION.fields[fieldObj.id].format == 'function')
				{
					if (typeof(fieldObj.onkeyup) == 'function')
					{
						window.FORMVALIDATION.prevkeyup[fieldObj.form.id] = fieldObj.onkeyup;
						//~ fieldObj.form.onkeyup =function(){ window.FORMVALIDATION.prevkeyup[fieldObj.id](); window.FORMVALIDATION.fields[fieldObj.id].format(fieldObj.id);  };
						fieldObj.form.onkeyup = new Function("window.FORMVALIDATION.prevkeyup['" + fieldObj.id + "'](); window.FORMVALIDATION.fields['" +fieldObj.id + "'].format('" + fieldObj.id + "');  ");
						
					
					}
					else
					{
						//~ fieldObj.onkeyup = window.FORMVALIDATION.fields[fieldObj.id].format;
						fieldObj.onkeyup = new Function("window.FORMVALIDATION.fields['" + fieldObj.id + "'].format('" + fieldObj.id + "'); ");
						
					}
				}
				else
				{
					// it is a string matching a predefined format function
					switch (window.FORMVALIDATION.fields[fieldObj.id].format.toString()) {
					
						case "numeric":
							formatVal = 'numeric';
							break;
						case "nospaces":
							formatVal = 'nospaces';
							break;
						case "alphanumeric":
							formatVal = 'alphanumeric';
							break;
						case "uppercase":
							formatVal = 'uppercase';
							break;
						case "lowercase":
							formatVal = 'lowercase';
							break;
						case "notags":
							formatVal = 'notags';
							break;
						
					}
					
					if (formatVal !== "")
					{
						if (typeof(fieldObj.onkeyup) == 'function')
						{
							window.FORMVALIDATION.prevkeyup[fieldObj.id] = fieldObj.onkeyup;
							//~ fieldObj.onkeyup = function(){window.FORMVALIDATION.prevkeyup[fieldObj.id](); window.FORMVALIDATION.format[formatVal](fieldObj.id); };
							fieldObj.onkeyup = new Function("window.FORMVALIDATION.prevkeyup['" + fieldObj.id + "'](); window.FORMVALIDATION.format." + formatVal + "('" + fieldObj.id + "');  ");
						
						}
						else
						{
							//~ fieldObj.onkeyup = window.FORMVALIDATION.format[formatVal]; 
							fieldObj.onkeyup = new Function("window.FORMVALIDATION.format." + formatVal + "('" + fieldObj.id + "'); ");
						}
					}
				}				
			}
		}
		// process all field settings
		for (var e in window.FORMVALIDATION.fields)
		{			
			if (document.getElementById(e))
			{
				fieldObj = document.getElementById(e);
					
				// attach form onsubmit
				attachOnsubmit();
				
				// attach format
				attachFormat();
			}
		}
	},	
	
	checkForm: function(formId)	{
		var tElement, eReturnVal, form, formcheck;
		
		form = document.getElementById(formId);
		formcheck = true;
		window.FORMVALIDATION.checkedfields = {};
		
		eachElement : for (var e = 0; e < form.elements.length; e++)
		{
			tElement = form.elements[e];
			eReturnVal = true;

			if ((tElement.id) && (this.fields[tElement.id]))
			{
				if (this.fields[tElement.id])
				{
					
					// do basic required check
					if (this.fields[tElement.id].required === true)
					{
						eReturnVal  = this.validate.required(tElement);
					}
					// process custom validation function if passed basic validation
					if (eReturnVal === true)
					{
						// perform special validation
						if (this.fields[tElement.id].validation)
						{
							// execute custom function
							if (typeof(this.fields[tElement.id].validation) == 'function')
							{
								try {
									eReturnVal = this.fields[tElement.id].validation(tElement.id);
								}
								catch(e1){
									//
									e1.action = "validation";
									e1.element = tElement.id;
									e1.elementType = "field";
									window.FORMVALIDATION.error(e1);
								}
								finally{
								
									if (window.FORMVALIDATION.stoponerror === true)
									{
										return false;
									}
								}
							}
							else // execute defined validation function
							{
								// if validation parameter is a valid validation function, execute it
								if (this.validate[this.fields[tElement.id].validation])
								{
									eReturnVal = this.validate[this.fields[tElement.id].validation](tElement);
								}
							}
						}
					}
					// check eReturnVal again and process success/fail functions
					// process custom failure function
					if (eReturnVal === false)
					{
						formcheck = false; // Trigger whole form error if one field validation error
						if (typeof(this.fields[tElement.id].failure) == 'function')
						{
							try {
								this.fields[tElement.id].failure(tElement.id);
							}
							catch(e2)
							{
								e2.action = "failure";
								e2.element = tElement.id;
								e2.elementType = "field";
								window.FORMVALIDATION.error(e2);
							}
							finally{
								
								if (window.FORMVALIDATION.stoponerror === true)
								{
									return false;
								}
							}
						}						
						break eachElement; // stop processing fields on first failure;
					}
					// process custom success function
					else if (eReturnVal === true)
					{					
						if ((this.fields[tElement.id].success) && (typeof(this.fields[tElement.id].success) == 'function'))
						{
							try {
								this.fields[tElement.id].success(tElement.id);
							}
							catch(e3)
							{
								e3.action = "success";
								e3.element = tElement.id;
								e3.elementType = "field";
								window.FORMVALIDATION.error(e3);
							}
							finally{
								
								if (window.FORMVALIDATION.stoponerror === true)
								{
									return false;
								}
							}
						}
					}
				}
			}			
			
		}
		// Trigger custom form success or failure functions
		if (formcheck === false)
		{
			if ((this.failure) && (typeof(this.failure) == 'function'))
			{
				try {
					this.failure();
				}
				catch(e4)
				{
					e4.action = "success";
					e4.element = formId;
					e4.elementType = "form";
					window.FORMVALIDATION.error(e4);
				}
				finally{
					if (window.FORMVALIDATION.stoponerror === true)
					{
						return false;
					}
				}
			}
		}		
		else if (formcheck === true)
		{
			if ((this.success) && (typeof(this.success) == 'function'))
			{
				try {
					this.success();
				}
				catch(e5)
				{
					e5.action = "success";
					e5.element = formId;
					e5.elementType = "form";
					window.FORMVALIDATION.error(e5);
				}
				finally{				
					if (window.FORMVALIDATION.stoponerror === true)
					{
						return false;
					}
				}
			}
		}
		
		return formcheck;
	
	},
	
	// define validation functions	
	validate: {
	
		// basic check for a value
		required:  function(field){
		
			// define check functions
			function checkText ()
			{
				if (field.value.length === 0){ return false; }
				return true;
			}

			function checkCheckbox ()
			{
				if (field.checked === false){ return false; }
				return true;   
			}
			
			function checkRadio ()
			{
				var optionSelected;
				// only check once, use checkedfields to prevent multiple
				if ((field.name) && (window.FORMVALIDATION.checkedfields[field.name] === undefined))
				{
					optionSelected = false;
					for (var i = 0; i < field.form[field.name].length; i++)
					{
						if (field.form[field.name][i].checked === true){ optionSelected = true; }
					}
					window.FORMVALIDATION.checkedfields[field.name] = 1;
					return optionSelected;   					
				}
				return true;
				
			}

			function checkSelect ()
			{
				if (field.selectedIndex < 1) { return false;}
				return true;				
			}
			
			function checkMultiselect ()
			{
				var optionSelected = false;
				for (var i = 0; i < field.length; i++)
				{
					if (field.options[i].selected)
					{
						optionSelected = true;
						break;
					}
				}				
				return optionSelected;
			}
			
			function alertField()
			{
		
				var fieldText = "";
				if (field.title) { fieldText = field.title; }
				else if (field.alt) { fieldText = field.alt; }
				else if (field.name) { fieldText = field.name; }
				else if (field.id) { fieldText = field.id; }
				else if (field.type) { fieldText = field.type; }
				else { return false; }
				
				alert(fieldText + ' is a required field. Please enter a value.');
				field.focus();
				return true;		
			}
			
			// begin validation logic
			var returnval = false;
			
			// convert ID names into objects
			if (typeof(field) != 'object')
			{	
				field = document.getElementById(field);
				if (!field)
				{
					return returnval;
				}			
			}
			
			switch (field.type) {
			
				case 'text':
					returnval =  checkText();
					break;
				
				case 'textarea':
					returnval =  checkText();
					break;
					
				case 'password':
					returnval =  checkText();
					break;
				
				case 'checkbox':
					returnval =  checkCheckbox();
					break;
					
				case 'radio':
					returnval =  checkRadio();
					break;
					
				case 'select-one':
					returnval =  checkSelect();
					break;
					
				case 'select-multiple':
					returnval =  checkMultiselect();
					break;
				
			}
			
			if (returnval === false)
			{
				if (window.FORMVALIDATION.failclass !== "")
				{					
					window.FORMVALIDATION.prevclassName[field.id] = field.className;
					field.className = field.className + ' ' + window.FORMVALIDATION.failclass;
				}
				
				if (window.FORMVALIDATION.alertrequired)
				{
					alertField();
				}
			}
			else if (returnval === true)
			{
				if (window.FORMVALIDATION.prevclassName[field.id] !== undefined)
				{
					field.className = window.FORMVALIDATION.prevclassName[field.id];
				}
			}
			
			return returnval;
			
		} // validate.required	
		
	},	// end validate
	
	format: {
	
		numeric: function(fieldId)
		{	// allow only numeric numbers to be typed
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.replace(/[^\d]/g, '');	
			return true;
		},
		
		nospaces: function(fieldId)
		{	// Allow any character except spaces
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.replace(/[\s]/g, '');			
			return true;
		},
		
		alphanumeric: function(fieldId)
		{	// alphanumeric
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.replace(/[^A-Za-z0-9_\-]/g, '');			
			return true;			
		},
		
		uppercase: function(fieldId)
		{	// uppercase all letters
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.toUpperCase();			
			return true;
		},
		
		lowercase: function(fieldId)
		{	// lowercase all letters
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.toLowerCase();			
			return true;
		},
		
		notags: function(fieldId)
		{	// remove html tags
			var fieldObj = document.getElementById(fieldId);
			fieldObj.value = fieldObj.value.replace(/(<([^>]+)>)/ig, '');		
			return true;
		}
	},
	
	error: function(errorObj){
		if (window.FORMVALIDATION.showerrors === true) {
			alert("FORMVALIDATION ERROR: \n\n" + errorObj.message + "\n\nDURING ACTION: " + errorObj.action + "\n\nFOR ELEMENT: " + errorObj.elementType + " " + errorObj.element);
		}
		return false;
	}
};

window.onerror=function(msg, file, line){
	alert("FORMVALIDATION detected the following error that may prevent normal software operation:\n\nError: " + msg + "\n\nFile: " + file + "\n\nLine: " + line);
};

// attach to onload but preserve existing onload function calls
if (typeof(window.onload) == 'function')
{
	window.FORMVALIDATION.prevonload = window.onload;
	window.onload = function(){ window.FORMVALIDATION.init(); window.FORMVALIDATION.prevonload();};
}
else
{
	window.onload = window.FORMVALIDATION.init;
}

