/* form.js by Panos Gkikakis - www.homersinn.net

You may use it even if you remove the header.
No support provided, this code is provided "as is".
Do whatever you want with it, if its usefull I would
appreciate a link to http://www.homersinn.net from your site(s).

Current support for numeric is integer number validation only!

Version 0.0 [Zero]
*/

/* PREDEFINED DO NOT TOUCH! */ 
Validator.prototype.invalid_text_max			= "Please enter at most %i characters.";
Validator.prototype.invalid_text_min			= "Please enter at least %i characters.";
Validator.prototype.invalid_numeric_max			= "Please enter a number smaller than %i.";
Validator.prototype.invalid_numeric_min			= "Please enter a number bigger than %i.";
Validator.prototype.invalid_date_max			= "Please enter a date before %s.";
Validator.prototype.invalid_date_min			= "Please enter a date after %s.";
Validator.prototype.invalid_numeric			= "Please enter a valid number.";
Validator.prototype.invalid_date			= "Please enter a valid date.";
Validator.prototype.email_only				= "Please enter a valid email address.";


/* Class Validator

Works like that:

Each form field has to have some 'rules' set. These are used for validation.

Rules:
validator_type	:
	'text'		: free text.
	'numeric'	: Item must be a number.
	'email'	 	: Item must be email.
	'optional'	: Item is ignored. 
	'date'		: Item must be a date.
	
validator_max	: 
	1) If type is email or text	: maximum number of characters.
	2) If type is numeric		: maximum number value.
	3) If type is date		: maximum date value.
	4) Otherwise ignored.

validator_min	: 
	1) If type is email or text	: minimum number of characters.
	2) If type is numeric		: minimum number value.
	3) If type is date		: minimum date value.
	4) Otherwise ignored.


validator_link_less	:
	1) Link to item which has to be of the same 'validator_type'. Rule dictates that this item's value must be smaller than the link's.

validator_link_greater	:
	1) Link to item which has to be of the same 'validator_type'. Rule dictates that this item's value must be greater than the link's.

validator_link_equal	:
	1) Link to item which has to be of the same 'validator_type'. Rule dictates that this item's value must be equal to the link's.

validator_realtime	:
	1) Turns on realtime validation. Validation will be performed automagically whenever the contents of the item changes.

TODO: 
1- Link more than 1 fields.
2- Realtime should clarify whether each keystroke is validated (and discarded or allowed) or if validation is perform upon onchange or other events.
*/


/*
Constructor

	settings:

		Array that contains the fields to validate and their rules.
		Array()
		{
					
			0 => Array() 	{	
						'field_name' => string
						'validator_type' => string
						... optional fields...
					}
			....
		
		}

*/

function Validator(form, settings)
{
	if(document.getElementById && form)
	{
		this.validator_form	= form;
		this.validator_nodes 	= new Array();
		this.validator_pop	= 0;
		
		if(settings && settings.length )
		{
			var node;

			for(var index=settings.length-1; index >=0; index--)
			{
				node = eval("this.validator_form."+(settings[index]['field_name'])+";");

				if(node)
					this.validify(node, settings[index]);

			}
		}
	}
}

/*
Settings can be an array with any of key => value:

validator_max		=>	number
validator_min		=>	number 
validator_link_less	=>	object
validator_link_greater	=>	object
validator_link_equal	=>	object
validator_type		=>	string [any of 'numeric', 'email'...etc]

etc. defined above.
*/

Validator.prototype.validify = function validify(node, settings) // nice naming eh?
{

	if(node && (node.value != null) && (settings != null) && (settings['validator_type'] != null))
	{
		this.validator_nodes[this.validator_pop] = node;
		this.validator_pop++;
		node.validator_object = this;
		extract_to(node, settings);
		
		if(node.validator_realtime)
		{
			node.onchange = function(e) { landon(e) };
		}
		return true;
	}

	return false;	
}

function landon(e)
{
	var node;
	if(!e)
	{
		e = window.event;
		node = e.srcElement;
	}
	else node = e.target;
	
	if(node && node.validator_object)
	{
		if(node.validator_object.validator_form && node.validator_object.validator_form.validator_realtime_all)
			node.validator_object.validate();
		else debug("Node's "+node.field_name + " validity is "+node.validator_object.validate_node(node));
	}
}

/*
Returns true or false.
If false then it writes red text to invalid fields.
Call Validator() first or the function will return false immediately.
*/

Validator.prototype.validate = function validate() 
{
	var ret = true;
	if(this.validator_nodes && this.validator_pop)
	{
		// traverse validator_nodes. 
		
		for(var c = 0; c < this.validator_pop; c++)
		{
			if(this.validate_node(this.validator_nodes[c]) != 1)
				ret = false;		
		}
	}
	
	if(this.submit_button)
		this.submit_button.disabled  = !ret; 	
		
	return ret;
}

Validator.prototype.validate_node = function validate_node(field)
{
	if(field && field.validator_type)
	{
		validation = eval("this.validate_node_"+field.validator_type+"(field);");
		
		if(document.getElementById && field.field_name)
		{
			out = document.getElementById(field.field_name+"_error");
			
			if(out && out.innerHTML != null)
			{
				if(validation == 0)
					out.innerHTML = "You must fill in " + (field.friendly_name != null ? field.friendly_name : "this field!");
				else if(validation == 1)
					out.innerHTML = "";
				else if(validation == 2)
					out.innerHTML = "Invalid " + (field.friendly_name != null ? field.friendly_name : "data!");
				else if(validation == 3)
				{
					if(field.validator_type == "text")
						out.innerHTML = field.friendly_name + " should be less than " + field.validator_max + " characters";
					else if(field.validator_type == "numeric")
						out.innerHTML = field.friendly_name + " should be less than " + field.validator_max;
					else if(field.validator_type == "date")
						out.innerHTML = field.friendly_name + " should be before " + field.validator_max.toDateString();
					else out.innerHTML = "The value you have entered exceeds maximum limit ("+field.validator_max+")";
					
				}
				else if(validation == 4)
				{
					if(field.validator_type == "text")
						out.innerHTML = field.friendly_name + " should be at least " + field.validator_min + " characters";
					else if(field.validator_type == "numeric")
						out.innerHTML = field.friendly_name + " should at least " + field.validator_min;
					else if(field.validator_type == "date")
						out.innerHTML = field.friendly_name + " should be after " + field.validator_min.toDateString();
					else out.innerHTML = "The value you have entered is less than the minimum limit ("+field.validator_min+")";
				}
				else if(validation == 5)
				{
					if(field.validator_type == "text")
						out.innerHTML = field.friendly_name + " should be longer than " + field.validator_link_less.friendly_name;
					else if(field.validator_type == "numeric")
						out.innerHTML = field.friendly_name + " should more than the value of " + field.validator_link_less.friendly_name;
					else if(field.validator_type == "date")
						out.innerHTML = field.friendly_name + " should be after " + field.validator_link_less.friendly_name;
					else out.innerHTML = "The value you have entered is greater than the maximum limit linked by field ("+field.validator_link_less.friendly_name+")";
				}
				else if(validation == 6)
				{
					if(field.validator_type == "text")
						out.innerHTML = field.friendly_name + " should be shorter than " + field.validator_link_greater.friendly_name;
					else if(field.validator_type == "numeric")
						out.innerHTML = field.friendly_name + " should less than the value of " + field.validator_link_greater.friendly_name;
					else if(field.validator_type == "date")
						out.innerHTML = field.friendly_name + " should be before " + field.validator_link_greater.friendly_name;
					else out.innerHTML = "The value you have entered is less than the minimum limit linked by field ("+field.validator_link_greater.friendly_name+")";
				}
				else if(validation == 7)
					out.innerHTML = field.friendly_name + " should be equal to " + field.validator_link_equal.friendly_name;
				else
					out.innerHTML = "Error in validating this field. Contact support.";
				
			}
		}
		
		return validation;
	}
	
	return false;
}


/*
Return values:
0 : Undefined.
1 : Valid value.
2 : Non numeric content.
3 : Greater than max.
4 : Less than min.
5 : validator_link_less is not less.
6 : validator_link_greater is not greater.
7 : validator_link_equal is not equal.
*/
Validator.prototype.validate_node_numeric = function validate_node_numeric(root)
{

	if(root && root.value)
	{	
		// STEP 1:
		
		for(var i = 0; i < root.value.length; i++)
		{
			if(root.value.charCodeAt(i) < 48 || root.value.charCodeAt(i) > 57)
				return 2;
		}
		
		// STEP 2:
		
		num_val = parseInt(root.value);
		
		if(isNaN(num_val))
			return 2;
		
		// Rules:
		
		if(root.validator_max && root.validator_max < num_val)
			return 3;
		if(root.validator_min && root.validator_min > num_val)
			return 4;	
		if(root.validator_link_less && root.validator_link_less.value && root.validator_link_less.value > num_val)
			return 5;
		if(root.validator_link_greater && root.validator_link_greater.value && root.validator_link_greater.value < num_val)
			return 6;
		if(root.validator_link_equal && root.validator_link_equal.value && root.validator_link_equal.value != num_val)
			return 7;
			
			
		
	} else return 0;
	
	return 1;
	
}

/*
Return values:
0 : Undefined.
1 : Valid value.
2 : Invalid email address.
*/
Validator.prototype.validate_node_email =  function validate_node_email(root)
{

	if(root && root.value)
	{
		if(this.isEmail(root.value))
			return 1;
		else return 2;
	} 
	else return 0;

}

/*
Return values:
0 : Undefined.
1 : Valid value.
2 : 
3 : Greater than max.
4 : Less than min.
5 : validator_link_less is not less.
6 : validator_link_greater is not greater.
7 : validator_link_equal is not equal.
*/
Validator.prototype.validate_node_text = function validate_node_text(root)
{
	if(root && root.value)
	{
		if(root.validator_max && root.validator_max < root.value.length)
			return 3;
		if(root.validator_min && root.validator_min > root.value.length)
			return 4;	
		if(root.validator_link_less && root.validator_link_less.value && root.validator_link_less.value.length && root.validator_link_less.value.length > root.value.length)
			return 5;
		if(root.validator_link_greater && root.validator_link_greater.value && root.validator_link_greater.value.length && root.validator_link_greater.value.length < root.value.length)
			return 6;
		if(root.validator_link_equal && root.validator_link_equal.value && root.validator_link_equal.value.length && root.validator_link_equal.value != root.value)
			return 7;
			
		return 1;
	} 
	else return 0;	
}

Validator.prototype.datesep = '/';

Validator.prototype.validate_node_date = function validate_node_date(root)
{
	if(root && root.value)
	{
		if(this.isDate(root.value))
		{
			// passed 1
			var parts = root.value.split(this.datesep);
			var date = new Date();
			date.setMonth(parseInt(parts[1])-1,  parseInt(parts[0]));
				
			if(date == NaN)
				return 2; // failed 2
				
			// pass back the values, in case we have a correction
			root.value = date.getDate() + "/" + (date.getMonth()+1);
			
			// passed 2 : valid date
			
			if(root.validator_max && root.validator_max < date)
				return 3;
			if(root.validator_min && root.validator_min > date)
				return 4;	
				
			var ldate = new Date();
			
			if(root.validator_link_less && root.validator_link_less.value && root.validator_link_less.value.length)
			{
				if(this.isDate(root.validator_link_less.value))
				{
					parts = root.validator_link_less.value.split(this.datesep);
					ldate.setMonth(parseInt(parts[1])-1,  parseInt(parts[0]));
					
					if(ldate != NaN)
					{
						if(!(date > ldate))
							return 5;
					}
				}
			}
			
			if(root.validator_link_greater && root.validator_link_greater.value && root.validator_link_greater.value.length)
			{
				if(this.isDate(root.validator_link_greater.value))
				{
					parts = root.validator_link_greater.value.split(this.datesep);
					ldate.setMonth(parseInt(parts[1])-1,  parseInt(parts[0]));
					
					if(ldate != NaN)
					{
						if(!(date < ldate))
							return 6;
					}
				}			
			}
			
			if(root.validator_link_equal && root.validator_link_equal.value && root.validator_link_equal.value.length)
			{
				if(this.isDate(root.validator_link_equal.value))
				{
					parts = root.validator_link_equal.value.split(this.datesep);
					ldate.setMonth(parseInt(parts[1])-1,  parseInt(parts[0]));

					if(ldate != NaN)
					{
						if(ldate != date)
							return 7;
					}
				}			
			}
		
		}
		else return 2;
		
		return 1;
	} 
	else return 0;	
}

Validator.prototype.isDate = function isDate(str)
{
	var filter  = /^([0-3]{0,1})([0-9])\/([0-1]{0,1})([0-9])$/;
	return (str.match(filter) != null);
}

Validator.prototype.isEmail = function isEmail(str)
{
		var filter  = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
		
/* OLD - changed to regexpr thanks to http://www.quirksmode.org/js/mailcheck.html

		var at="@"
		var dot="."
		var tmp = str.split(at);
		var username = tmp[0] ? tmp[0] : false;
		tmp = (tmp[1] ? tmp[1].split(dot) : false);
		var domain = tmp[0] ? tmp[0] : false;
		var extension = tmp[1] ? tmp[1] : false;
		
		return (username && domain && extension && (username.length > 0) && (domain.length >0) && (extension.length >0));
*/	
		return (str.match(filter) != null);
}

extract_to = function extract_to(target_object, array)
{
	if(target_object && array)
	{
		for(key in array)
		{
			eval("target_object." + key+" = array[key];");
		}
	}
}

extract = function extract(array)
{
	extract_to(this, array);
}

function debug(print)
{

	if((print != null) && document.getElementById)
	{
		if(d = document.getElementById(this.debug_element))
		{
			if(d.innerHTML != null)
			{
				if(typeof print == 'object')
				{
					for(var x in print)
						d.innerHTML += '<p>['+x+'] => ['+print[x]+"]</p>"; 
				}
				else
					d.innerHTML += '<p>'+print.toString()+'</p>';
			}
		}
	}
}
