// sm-validation.js - Form Validation Functions - Copyright (c) 2006 ScriptingMagic.com

if (typeof ScriptingMagic == 'undefined') var ScriptingMagic = {};

/***************************************************************************
 *
 *  Functions for form validation
 *
 ***************************************************************************/

if (typeof ScriptingMagic.Validation == 'undefined')
{
	ScriptingMagic.Validation = {
		filename: 'sm-validation.js',
		version: 1.0,
		release: new Date('Nov 2, 2006'),
		requires: [{filename: 'sm-common.js', object: 'ScriptingMagic.Common'}],
	
		checkLibs: function()
		{
			var i, missing = [];
			
			for (i = 0; i < this.requires.length; i++)
				if (typeof eval(this.requires[i].object) == 'undefined')
					missing[missing.length] = this.requires[i].filename;
	
			if (missing.length == 0)
				return true;

			var message = this.filename + ' requires the following librar' + (missing.length == 1 ? 'y' : 'ies') + ': ';
			for (i = 0; i < missing.length; i++)
				message += (i == 0 ? '' : ', ') + missing[i];
				
			message += '\n\nYou can download ' + (missing.length == 1 ? 'it' : 'them') +
						' for free from ScriptingMagic.com. Would you like to go there now?'

			if (confirm(message))
				document.location = "http://www.scriptingmagic.com/";
	
			return false;
		},
		
		VALID: 0,
		INFO: 1,
		WARN: 2,
		ERROR: 3,
		
		formats: {
			alpha:			{ pattern: /^[ a-zA-Z]+$/,				mask: /^([ a-zA-Z]+)?$/ },
			alphanumeric:	{ pattern: /^[ a-zA-Z0-9]+$/,			mask: /^([ a-zA-Z0-9]+)?$/ },
			decimal:		{ pattern: /^[-]?(\d+)|(\d*[.]\d+)$/,	mask: /^[-]?((\d+)|(\d*[.](\d+)?))?$/ },
			email:			{ pattern: /^[-a-zA-Z0-9_.]+@([-a-zA-Z0-9]+[.])+([a-zA-Z0-9]{2,4})$/, mask: /^([-a-zA-Z0-9_.]+(@(([-a-zA-Z0-9]+[.]?)+([a-zA-Z0-9]{2,4})?)?)?)?$/ },
			integer:		{ pattern: /^[-]?\d+$/,					mask: /^[-]?(\d+)?$/ },
			numeric:		{ pattern: /^\d+$/,						mask: /^(\d+)?$/ }
		},
		
		addFormat: function(format, pattern, mask, validator)
		{
			if (!format || !pattern || (validator && (typeof validator != 'function')))
				return alert('Usage: addFormat(format, pattern[, mask[, validator]])');
				
			if (typeof pattern == 'string')
				pattern = new RegExp(pattern);
				
			if (mask && (typeof mask == 'string'))
				mask = new RegExp(mask);

			ScriptingMagic.Validation.formats[format.toLowerCase()] = { pattern: pattern, mask: mask, validator: validator };
		},
		
		createMessage: function(level, message)
		{
			return { level: level, message: message };
		},
		
		init: function(element)
		{
			if (!element || !element.tagName)
			{
				var i, elements = [];
	
				findElements('textarea', null, elements);
				findElements('input', null, elements);
				findElements('form', null, elements);
				findElements('select', null, elements);
				
				for (i = 0; i < elements.length; i++)
					ScriptingMagic.Validation.init(elements[i]);
			}
			else
			{
				switch (element.tagName)
				{
					case 'FORM':
						// We have to save the submit function for later
						if (!element['real submit function'])
							element['real submit function'] = element.submit;
							
						var handler = function() { return ScriptingMagic.Validation.submitHandler.apply(element, arguments); };
						element.submit = handler;
						addEventHandler(element, 'submit', handler);
						
						addEventHandler(element, 'reset', function() { setTimeout(function() { ScriptingMagic.Validation.resetHandler.apply(element, []); }, 50); });
						break;
					case 'INPUT':
					case 'TEXTAREA':
						addEventHandler(element, 'keypress', ScriptingMagic.Validation.filterKeyPress);
						addEventHandler(element, 'keyup', ScriptingMagic.Validation.checkValidation);
						addEventHandler(element, 'click', ScriptingMagic.Validation.checkValidation);
						ScriptingMagic.Validation.checkValidation.apply(element, []);
						break;
					case 'SELECT':
						addEventHandler(element, 'change', ScriptingMagic.Validation.checkValidation);
						addEventHandler(element, 'keyup', ScriptingMagic.Validation.checkValidation);
						ScriptingMagic.Validation.checkValidation.apply(element, []);
						break;
				}
				
				var validateOn = element.getAttribute('sm:validateOn');
				
				if (validateOn)
				{
					validateOn = validateOn.replace(/\s+/, ' ').split(' ');
					
					var i, target, fn = function() { ScriptingMagic.Validation.checkValidation.call(element); };
					
					for (i = 0; i < validateOn.length; i++)
					{
						target = document.getElementById(validateOn[i]);
						
						if (target)
						{
							addEventHandler(target, 'click', fn);
							addEventHandler(target, 'keyup', fn);
							addEventHandler(target, 'change', fn);
						}
						else
							alert('Couldn\'t find ' + validateOn[i] + ' in DOM.'+i+'<--Index and target-->'+target);
					}
				}
			}
		},
		
		getValidationInfo: function(element)
		{
			var required = element.getAttribute('sm:required'), i, buttons;
			
			var name = ScriptingMagic.Common.findLabel(element);
			
			var inputType = (element.tagName == 'INPUT') && (element.getAttribute('type') || 'text').toLowerCase();
			
			if (!required && (inputType == 'radio'))
			{
				buttons = element.form.elements[element.name];
				
				var button;
				
				for (i = 0; i < buttons.length; i++)
				{
					button = buttons[i];
					required = required || button.getAttribute('sm:required');
				}
			}
			
			if ((!required || !eval(required)) && (element.value === ''))
				return {level:ScriptingMagic.Validation.VALID};

			var format = element.getAttribute('sm:format');
			
			var info = 	{ 	level: ScriptingMagic.Validation.VALID,
							element: element,
							name: name
						};
			
			if (format)
				format = ScriptingMagic.Validation.formats[format.toLowerCase()];

			var validator, error = false, message = '';

			if (info.level < ScriptingMagic.Validation.ERROR)				
			{
				if (element.form && element.form.getAttribute)
				{
					validator = element.getAttribute('sm:validator') || element.form.getAttribute('sm:validator');
				
					if (validator)
						ScriptingMagic.Common.callUserDefinedFunction.apply(element, [validator, [info]]);
				}
			}

			if (required && ScriptingMagic.Validation.isEmpty(element))
			{
				reason = ' ';
				error = ' is required';
			}

			var value = element.value;

			if (!error)
			{
				var minValue = element.getAttribute('sm:minValue');

				if (typeof minValue == 'string')
				{
					minValue = Number(minValue);
					
					if (Number(value) < minValue)
					{
						reason = 'too small';
						error = ' must be greater than or equal to ' + minValue;
					}
				}
			}

			if (!error)
			{
				var maxValue = element.getAttribute('sm:maxValue');
				
				if (typeof maxValue == 'string')
				{
					maxValue = Number(maxValue);
					
					if (Number(value) > maxValue)
					{
						reason = 'too large';
						error = ' must be less than or equal to ' + maxValue;
					}
				}
			}
			
			if (!error)
			{
				var minLength = element.getAttribute('sm:minLength');
				
				if (typeof minLength == 'string')
				{
					minLength = Number(minLength);
					
					if (value.length < minLength)
					{
						reason = 'too short';
						error = ' must be at least ' + minLength + ' characters long';
					}
				}
			}
			
			if (!error)
			{
				var maxLength = element.getAttribute('sm:maxLength');
					
				if (typeof maxLength == 'string')
				{
					maxLength = Number(maxLength);
					
					if (value.length > maxLength)
					{
						reason = 'too long';
						error = ' must be less than or equal to ' + maxLength + ' characters long';
					}
				}
			}

			if (!error)
			{
				var pattern = element.getAttribute('sm:pattern');

				if (pattern)
					pattern = new RegExp(pattern);
				else if (format)
					pattern = format.pattern;

				if (pattern && !value.match(pattern))
				{
					reason = 'Invalid';
					error = ' is invalid';
				}
			}

			if (error)
			{
				if (info.level < ScriptingMagic.Validation.ERROR)
				{
					info = 	{ 	level: ScriptingMagic.Validation.ERROR,
								element: element,
								reason: reason,
								name: name,
								message: name + error
							};
					
				}
			}

			if (info.level < ScriptingMagic.Validation.ERROR)
			{
				if (format)
				{
					validator = format.validator;
	
					if (validator)
					{
						var newInfo = {	level: ScriptingMagic.Validation.VALID,
										element: element,
										name: name
									  };
	
						ScriptingMagic.Common.callUserDefinedFunction.apply(element, [validator, [newInfo]]);
	
						if (info.level < newInfo.level)
							info = newInfo;
					}
				}
			}
			
			if (info.level > ScriptingMagic.Validation.VALID)
				return info;

			return {level: ScriptingMagic.Validation.VALID};
		},
		
		submitHandler: function(e)
		{
			var i, validationFailures = [], info, element, errorCount = 0, warnCount = 0;
			
			for (i = 0; i < this.elements.length; i++)
			{
				element = this.elements[i];
				
				if ((element.tagName == 'INPUT') && ((element.type || 'text').toLowerCase() == 'radio') && (!element.getAttribute('sm:required')))
					continue;

				info = ScriptingMagic.Validation.getValidationInfo(this.elements[i]);

				if (info.level > ScriptingMagic.Validation.VALID)
					validationFailures[validationFailures.length] = info;
					
				switch (info.level)
				{
					case ScriptingMagic.Validation.ERROR:
						errorCount++;
						break;
					case ScriptingMagic.Validation.WARN:
						warnCount++;
						break;
				}
			}	

			if (errorCount == 0)
				return this['real submit function']();

			var handler = this.getAttribute('sm:onInvalid');
			
			if (handler)
				handler = eval(handler);
			else
				handler = ScriptingMagic.Validation.onInvalid;
				
			if (!handler.apply(this, validationFailures))
			{			
				var first = validationFailures[0].element;
			
				first.focus();
			
				if (first.select)
					first.select();
			}
			
			if (e && e.preventDefault)
				e.preventDefault();
				
			return false;
		},
		
		resetHandler: function()
		{
			var i;
			
			for (i = 0; i < this.elements.length; i++)
				ScriptingMagic.Validation.checkValidation.apply(this.elements[i], []);
		},
		
		onInvalid: function()
		{
			var attribute = this.getAttribute('sm:onInvalid');
			
			if (attribute)
				return ScriptingMagic.Common.callUserDefinedFunction.apply(this, [attribute, arguments]);

			if (this.tagName == 'FORM')
			{
				var i, message = 'Please correct the following error' +
									(arguments.length > 1 ? 's' : '') +
									':\n', item, name;
			
				for (i = 0; i < arguments.length; i++)
				{
					info = arguments[i];

					if (info.level > ScriptingMagic.Validation.WARN)
						message += '\n' + info.message;
				}
				
				alert(message);
			}
		},
		
		onValid: function()
		{
			try
			{
				var attribute = this.getAttribute('sm:onValid');

				if (attribute)
					return ScriptingMagic.Common.callUserDefinedFunction.apply(this, [attribute, arguments]);
			}
			catch (e) { alert(this.tagName + ' ' + e); }
		},
		
		isEmpty: function(element)
		{
			switch (element.tagName)
			{
				case 'INPUT':
				case 'TEXTAREA':
					switch ((element.getAttribute('type') || 'text').toLowerCase())
					{
						case 'radio':
							var i, list = element.form.elements[element.name];
							
							for (i = 0; i < list.length; i++)
								if (list[i].checked)
									return false;
									
							return true;
						case 'text':
						case 'textarea':
							return element.value === '';
						case 'checkbox':
							return !element.checked;
					}
					break;
				case 'SELECT':
					var index = element.selectedIndex;
					var value = element.selectedIndex == -1 ? null : element.options[index].value;
					
					return (index == -1) ||
							(value === '');
			}

			return false;
		},
		
		checkValidation: function(e)
		{
			var info = ScriptingMagic.Validation.getValidationInfo(this);
			
			var lastInfo = this['last validation info'];
			
			if (lastInfo && (info.level == lastInfo.level) && (info.reason == lastInfo.reason) && (info.message == lastInfo.message))
				return;
				
			this['last validation info'] = info;
			
			var i, element;
				
			var list = [this];
			
			var l = ScriptingMagic.Common.locateLabel(this.id);
			
			if (l)
				list[list.length] = l;
				
			if ((this.tagName == 'INPUT') && ((this.getAttribute('type') || 'text').toLowerCase() == 'radio'))
			{
				var buttons = this.form.elements[this.name], button;

				for (i = 0; i < buttons.length; i++)
				{
					button = buttons[i];
					
					if (button == this)
						continue;

					list[list.length] = button;
					
					l = ScriptingMagic.Common.locateLabel(button.id);
					
					if (l)
					{
						list[list.length] = l;
					}
				}
			}
			
			var classes = ['valid', 'valid-info', 'valid-warn', 'invalid'];
			
			var j;
			
			for (i = 0; i < list.length; i++)
			{
				element = list[i];
				
				for (j = 0; j < classes.length; j++)
				{
					if (j != info.level)
						removeClass(element, classes[j]);
					else
						addClass(element, classes[j]);
				}

				if (info.level < ScriptingMagic.Validation.ERROR)
					ScriptingMagic.Validation.onValid.apply(element, [info]);
				else
					ScriptingMagic.Validation.onInvalid.apply(element, [info]);
			}
		},
		
		filterKeyPress: function(e)
		{
			var undefined;
	
			var mask = e.target.getAttribute('sm:mask');

			if (mask)
				mask = new RegExp(mask);
			else
			{
				var format = e.target.getAttribute('sm:format');
			
				if (!format)
					return undefined;

				format = ScriptingMagic.Validation.formats[format.toLowerCase()];
				
				if (format)
					mask = format.mask;
			}
			
			if (!mask)
				return undefined;
			
			var c = e.charCode;
			
			if ((c < 32) || (c >= 63232) || e.altKey || e.ctrlKey || e.metaKey)
				c = '';
			else
				c = String.fromCharCode(c);

			ScriptingMagic.Common.getSelectionIndices(e.target);
			
			var start = e.target.selectionStart;
			var end = e.target.selectionEnd;
			
			var value = e.target.value;
			
			var string = '';
			
			if (start > 0)
				string = value.substring(0, start);
				
			string += c;
			
			if (end < value.length)
				string += value.substring(end);
				
			if (!string.match(mask))
			{
				e.preventDefault();
				return false;
			}
				
			return undefined;
		},
		
		creditCardValidator: function(info)
		{
			if (this.cardType)
				delete this.cardType;

			var number = this.value;
			
			if (number.length == 0)
				return;
			
			if ((number.length > 0) && (number.length < 13 || number.length > 16))
			{
				info.level = ScriptingMagic.Validation.ERROR;
				info.message = info.name + " is invalid";
			}

			var sum = 0, i, length, v;
			
			for (i = 0, length = number.length; i < length; i++)
			{
				v = Number(number.charAt(length - i - 1));

				if (i % 2 == 1)
					v *= 2;

				sum += Math.floor(v / 10) + v % 10;
			}
			var cardType = ScriptingMagic.Validation.getCardType(number)

			if ((sum % 10 != 0) || !cardType)
			{
				info.level = ScriptingMagic.Validation.ERROR;
				info.reason = 'invalid';
				info.message = info.name + ' is invalid';
				return;
			}
			
			this.cardType = cardType;
			info.cardType = cardType;
		},
		
		getCardType: function(number)
		{
			if (ScriptingMagic.Validation.checkCard(number, 15, "34", "37"))
				return "AMEX";
			
			if (ScriptingMagic.Validation.checkCard(number, 14, "30", "36", "38"))
				return "DinersClub";
				
			if (ScriptingMagic.Validation.checkCard(number, 16, "6011"))
				return "Discover";
			
			if (ScriptingMagic.Validation.checkCard(number, 15, "2014", "2149"))
				return "enRoute";
			
			if (ScriptingMagic.Validation.checkCard(number, 16, "3088","3096","3112","3158","3337","3528"))
				return "JCB";
			
			if (ScriptingMagic.Validation.checkCard(number, 16, "51", "52", "53", "54", "55"))
				return "MasterCard";
			
			if (ScriptingMagic.Validation.checkCard(number, 13, "4") || ScriptingMagic.Validation.checkCard(number, 16, "4"))
				return "VISA";

			return null;
		},
		
		checkCard: function(number, length)
		{
			if (number.length != length)
				return false;
			
			var i, prefix;

			for (i = 2; i < arguments.length; i++)
			{
				prefix = arguments[i];
				if (number.substring(0, prefix.length) == prefix)
					return true;
			}
			
			return false;
		}
		
	};

	ScriptingMagic.Validation.addFormat('creditcard', /^[-0-9 ]{13,16}$/, /^\d{0,16}$/, ScriptingMagic.Validation.creditCardValidator);
	
	if (ScriptingMagic.Validation.checkLibs())
		addEventHandler(window, 'load', ScriptingMagic.Validation.init);
}