/*==============================================================
//validator.js
//By: Russell S. Ahlstrom
//Last Update: 02 Mar 2008
//
//Creates a validation with a specific scope. FormElements within that scope can be manual validated or automatically 
//validated when their values change or their form is submitted. Custom validation methods can be passed to the object along 
//with custom error handling.
//
//Requires prototype.js library
//
//==============================================================
//'Public' Functions
//==============================================================
//
//var validator = new validator(string idOfAFormOrOtherContainerTag, JSON validatorOptions);
//validator.validate(string idOfAFormField);
//validator.validateAll();
//validator.validation_failed(string errorCode, string errorMsg);
//
//----------------
//validatorOptions
//----------------
//
//boolean onChange - Validate each form field when its value changes. Defaults to false.
//boolean onSubmit - Validate everything in the validator's scope on a form submit. Defaults to false.
//function onPreValidate - Function that runs automatically for every form field before it is validated. This function is called with element formElement as its argument. Defaults to false.
//function customValidation - Function that runs automatically during a form field validation to check for custom validation conditions. element formElement is passed as an argument. Defaults to false.
//function errorHandler - Function to call in event of a validation error. element formElement and JSON {code: errorCode, message: errorMsg} are passed as arguments to the function. If none specified, then output the error in an alert box.
//function onSubmitStart
//function onSubmitFinished
//
==============================================================*/

function validator(scope, validatorOptions) {
	if(!validatorOptions) {
		validatorOptions = {};
	}
	
	this.scope = $(scope);
	this.form = this.getForm();
	this.errors = {code: [], message: []};
	this.validation_running = false;
	this.validation_queue = [];
	this.formElements_checked = [];
	this.radioCheckElements_checked = [];
	this.formElement_passed_validation = true;
	
	this.onChange = validatorOptions.onChange || false;
	this.onSubmit = validatorOptions.onSubmit || false;
	this.onSubmitStart = validatorOptions.onSubmitStart || false;
	this.onSubmitFinish = validatorOptions.onSubmitFinish || false;
	this.onPreValidate = validatorOptions.onPreValidate || this.prevalidate;
	this.customValidation = validatorOptions.customValidation || false;
	this.errorHandler = validatorOptions.errorHandler || this.error_handler;
	
	if(this.onChange) {
		this.addOnChangeFunctions();
	}
	if(this.onSubmit) {
		this.addOnSubmitFunction();
	}
}

validator.prototype.getForm = function() {
	//If the scope is the same as the form, just return the scope
	if(this.scope.tagName.toLowerCase() == "form") {
		return this.scope;
	}
	
	//Look for an input and if found return that input's form
	var inputs = this.scope.getElementsByTagName("input");
	if(inputs.length > 0) {
		return inputs[0].form;
	}
	
	//Look for a select and if found return that select's form
	var selects = this.scope.getElementsByTagName("select");
	if(selects.length > 0) {
		return selects[0].form;	
	}
	
	//Look for a textarea and if found return that textarea's form
	var textareas = this.scope.getElementsByTagName("textarea");
	if(textareas.length > 0) {
		return textareas[0].form;
	}
	
	//If we've gotten this far, something crazy is going on but let's go up through the DOM until we find a form
	var parent = this.scope.parentNoe;
	while(parent.tagName.toLowerCase() != "form") {
		parent = parent.parentNode
		//If we've gotten to the body tag, then we won't find a from tag ever. Quit looping and return null.
		if(parent.tagName.toLowerCase() == "body") {return null;};
	}
	return parent;
}

validator.prototype.getScopeElements = function() {
	var inputs = this.scope.getElementsByTagName("input");
	var validInputs = $A(inputs).findAll(
		function (input) {
			return (input.type.toLowerCase() == "text" || input.type.toLowerCase() == "radio" || input.type.toLowerCase() == "checkbox" || input.type.toLowerCase() == "password");
		}
	)
	var selects = $A(this.scope.getElementsByTagName("select"));
	var textareas = $A(this.scope.getElementsByTagName("textarea"));
	var scopeElements = [].concat(validInputs,selects,textareas);
	
	return scopeElements;
}

validator.prototype.addOnSubmitFunction = function() {
	if(this.form == null) {return false;};
	Event.observe(this.form, "submit", this.validateSubmit.bindAsEventListener(this));
}

validator.prototype.addOnChangeFunctions = function() {
	var scopeElements = this.getScopeElements();
	
	for(var i = 0; i < scopeElements.length; i++) {
		if(scopeElements[i].type.toLowerCase() == "radio" || scopeElements[i].type.toLowerCase() == "checkbox") {
			Event.observe(scopeElements[i], "click", this.validate.bind(this, scopeElements[i]));
		}
		else {
			Event.observe(scopeElements[i], "change", this.validate.bind(this, scopeElements[i]));
		}
	}
}

validator.prototype.prevalidate = function(formElement) {
	if($(formElement.id + "_errorText")) {
		$(formElement.id + "_errorText").remove();
	}
}

validator.prototype.error_handler = function(formElement, errors) {
	var parent = formElement.up("td");
	var errorDiv = new Element("div", {'id': formElement.id + "_errorText", 'class': "error-message"}).update(this.humanize_name(formElement) + " " + errors.message[0]);
	parent.insert(errorDiv);
}

validator.prototype.validation_failed = function(errorCode, errorMsg) {
	this.formElement_passed_validation = false;	
	this.errors.code.push(errorCode);
	this.errors.message.push(errorMsg);
}

validator.prototype.clear_validation = function() {
	this.formElement_passed_validation = true;	
	this.errors = {code: [], message: []};
}

validator.prototype.get_validation = function() {
	return this.formElement_passed_validation;	
}

validator.prototype.get_errors = function() {
	return this.errors;	
}

validator.prototype.validateSubmit = function(event) {
	//Stop the submission event no matter what
	Event.stop(event);
	
	if(this.onSubmitStart) {this.onSubmitStart(this.form);}
	
	var passed_validation = this.validateAll();
	
	if(this.onSubmitFinish) {
			this.onSubmitFinish(this.form, passed_validation);
	}
	else {
		//Restart the submission event only if they passed validation and there isn't custom onSubmitFinished
		if(passed_validation) {this.form.submit();};
	}
}

validator.prototype.validateAll = function() {
	//If an onChange validation is running, consider it not to be running to avoid conflicts between an onChange check and an onSubmit check.
	this.validation_running = false;
	var passed_validation = true;
	
	var scopeElements = this.getScopeElements();
	for(var i = 0; i < scopeElements.length; i++) {
		if(this.radioCheckElements_checked.indexOf(scopeElements[i].name) == -1) {
			if(!this.validate(scopeElements[i])) {
				passed_validation = false;
			};
			if(scopeElements[i].type.toLowerCase() == "radio" || scopeElements[i].type.toLowerCase() == "checkbox") {
				this.radioCheckElements_checked.push(scopeElements[i].name);
			};
		};
	}
	
	this.radioCheckElements_checked = [];
	return passed_validation;
}

validator.prototype.validate = function(formElement) {
	formElement = $(formElement);
	//If there is a validation running, queue the next formElement requesting validation
	if(this.validation_running) {
		//Only queue the next formElement requesting validation if it hasn't already been validated
		if(this.formElements_checked.indexOf(formElement) == -1 && this.validation_queue.indexOf(formElement) == -1) {
			this.validation_queue.push(formElement);
		}
		return true;
	}
	
	this.validation_running = true;
	
	var passed_validation = this.run_validation(formElement);
	
	//Validate everything in the queue directly
	while(this.validation_queue.length > 0) {
		this.run_validation($(this.validation_queue.shift()));
	}
	
	//Clear validatoin running flag and the list of elements checked this run through
	this.validation_running = false;
	this.formElements_checked = [];
	
	return passed_validation;
}

validator.prototype.run_validation = function(formElement) {
	formElement = $(formElement);
	//Mark this formElement as checked
	this.formElements_checked.push(formElement);
	this.clear_validation();
	var classNames = formElement.className.split(" ");
	
	if(this.onPreValidate) {
		this.onPreValidate(formElement);
	}
	
	if(classNames.indexOf("required") != -1) {
		if(!this.is_required(formElement)) {
			this.validation_failed("required", "is required.");
		}
	}
	
	if(classNames.indexOf("blankValue") != -1) {
		if(!this.is_blank(formElement)) {
			this.validation_failed("blank", "must be blank.");
		}
	}
	
	if(classNames.indexOf("numericValue") != -1) {
		if(!this.is_numeric(formElement)) {
			this.validation_failed("numeric", "must be a number.");
		}
	}
	
	if(classNames.indexOf("numericPositiveValue") != -1) {
		if(!this.is_numeric_positive(formElement)) {
			this.validation_failed("numeric_positive", "must be a positive number.");
		}
	}
	
	if(classNames.indexOf("numericNegativeValue") != -1) {
		if(!this.is_numeric_negative(formElement)) {
			this.validation_failed("numeric_negative", "must be a negative number.");
		}
	}
	
	if(classNames.indexOf("integerValue") != -1) {
		if(!this.is_integer(formElement)) {
			this.validation_failed("integer", "must be a whole number.");
		}
	}
	
	if(classNames.indexOf("integerPositiveValue") != -1) {
		if(!this.is_integer_positive(formElement)) {
			this.validation_failed("integer_positive", "must be a positive whole number.");
		}
	}
	
	if(classNames.indexOf("integerNegativeValue") != -1) {
		if(!this.is_integer_negative(formElement)) {
			this.validation_failed("integer_negative", "must be a negative whole number.");
		}
	}
	
	if(classNames.indexOf("integerNaturalValue") != -1) {
		if(!this.is_integer_natural(formElement)) {
			this.validation_failed("integer_natural", "must be a whole number greater than zero.");
		}
	}
	
	if(classNames.indexOf("alphaValue") != -1) {
		if(!this.is_alpha(formElement)) {
			this.validation_failed("alpha", "must only contain alphabetic characters.");
		}
	}
	
	if(classNames.indexOf("alphanumericValue") != -1) {
		if(!this.is_alphanumeric(formElement)) {
			this.validation_failed("alphanumeric", "must only contain alphanumeric characters.");
		}
	}
	
	if(classNames.indexOf("dateValue") != -1) {
		if(!this.is_date(formElement)) {
			this.validation_failed("date", "must be a date in the mm/dd/yyyy format.");
		}
	}
	
	if(classNames.indexOf("timeValue") != -1) {
		if(!this.is_time(formElement)) {
			this.validation_failed("time", "must be a time in the hh:mm:ss tt format. ss and tt are optional.");
		}
	}
	
	if(classNames.indexOf("datetimeValue") != -1) {
		if(!this.is_datetime(formElement)) {
			this.validation_failed("datetime", "must be a date in the mm/dd/yyyy hh:mm:ss tt format. ss and tt are optional.");
		}
	}
	
	if(classNames.indexOf("urlValue") != -1) {
		if(!this.is_url(formElement)) {
			this.validation_failed("url", "must be a valid Internet address.");
		}
	}
	
	if(classNames.indexOf("ssnValue") != -1) {
		if(!this.is_ssn(formElement)) {
			this.validation_failed("ssn", "must be a valid US Social Security Number.");
		}
	}
	
	if(classNames.indexOf("emailValue") != -1) {
		if(!this.is_email(formElement)) {
			this.validation_failed("email", "must be a valid email address.");
		}
	}
	
	if(classNames.indexOf("zipValue") != -1) {
		if(!this.is_zip(formElement)) {
			this.validation_failed("zip", "must be a valid US zip code.");
		}
	}
	
	if(classNames.indexOf("canadianPostalValue") != -1) {
		if(!this.is_canadian_postal(formElement)) {
			this.validation_failed("canadian_postal", "must be a valid Canadian postal code.");
		}
	}
	
	if(classNames.indexOf("ukPostalValue") != -1) {
		if(!this.is_uk_postal(formElement)) {
			this.validation_failed("uk_postal", "must be a valid UK postal code.");
		}
	}
	
	if(classNames.indexOf("postalZipValue") != -1) {
		if(!this.is_postal_zip(formElement)) {
			this.validation_failed("postal_zip", "must be a valid US Zip Code, Canadian postal code, or UK postal code.");
		}
	}
	
	if(classNames.indexOf("phoneValue") != -1) {
		if(!this.is_phone(formElement)) {
			this.validation_failed("phone", "must be a valid US phone number.");
		}
	}
	
	if(classNames.indexOf("isbnValue") != -1) {
		if(!this.is_isbn(formElement)) {
			this.validation_failed("isbn", "must be a valid ISBN. Valid ISBNs consist of ten characters. These characters are seperated into four groups seperated from other groups by hyphons.");
		}
	}
	
	if(classNames.indexOf("moneyValue") != -1) {
		if(!this.is_money(formElement)) {
			this.validation_failed("money", "must be a valid dollar value.");
		}
	}
	
	if(classNames.indexOf("moneyPositiveValue") != -1) {
		if(!this.is_money_positive(formElement)) {
			this.validation_failed("money_positive", "must be a valid positive dollar value.");
		}
	}
	
	if(classNames.indexOf("moneyNegativeValue") != -1) {
		if(!this.is_money_negative(formElement)) {
			this.validation_failed("money_negative", "must be a valid negative dollar value.");
		}
	}
	
	if(classNames.indexOf("byuIdValue") != -1) {
		if(!this.is_byu_id(formElement)) {
			this.validation_failed("byu_id", "must be a valid BYU ID (NN-NNN-NNNN).");
		}
	}
	
	if(classNames.indexOf("passwordValue") != -1) {
		if(!this.is_password(formElement)) {
			this.validation_failed("password", "must be at least eight characters and contain at least one number.");
		}
	}
	
	if(classNames.indexOf("maxLength") != -1) {
		if(!this.is_maxLength(formElement)) {
			this.validation_failed("maxLength", "must be " + formElement.maxLength + " characters long.");
		}
	}
	
	if(formElement.className.indexOf("minLength") == 0 || formElement.className.indexOf(" minLength") > 0) {
		if(!this.is_minLength(formElement)) {
			var minLength = this.parse_out_number(formElement, "minLength");
			this.validation_failed("minLength", "must be at least " + minLength + " characters long.");
		}
	}
	
	if(formElement.className.indexOf("minOptionChoice") == 0 || formElement.className.indexOf(" minOptionChoice") > 0) {
		if(!this.is_minOptionChoice(formElement)) {
			var minOptionChoice = this.parse_out_number(formElement, "minOptionChoice");
			this.validation_failed("minOptionChoice", "must have at least " + minOptionChoice + " choice(s) selected.");
		}
	}
	
	if(formElement.className.indexOf("maxOptionChoice") == 0 || formElement.className.indexOf(" maxOptionChoice") > 0) {
		if(!this.is_maxOptionChoice(formElement)) {
			var maxOptionChoice = this.parse_out_number(formElement, "maxOptionChoice");
			this.validation_failed("maxOptionChoice", "can only have " + maxOptionChoice + " choice(s) selected.");
		}
	}
	
	if(formElement.className.indexOf("maxFloat") == 0 || formElement.className.indexOf(" maxFloat") > 0) {
		if(!this.is_maxFloat(formElement)) {
			var maxFloat = this.parse_out_number(formElement, "maxFloat");
			this.validation_failed("maxFloat", "can only have " + maxFloat + " decimal places.");
		}
	}
	
	if(this.customValidation) {
		this.customValidation(formElement);
	}
	
	if(this.get_validation() == false && this.errorHandler) {
		this.errorHandler(formElement, this.get_errors());
	}
	
	return this.get_validation();
}

validator.prototype.is_required = function(formElement) {
	//Value cannot be null
	return $F(formElement) != "";
}

validator.prototype.is_blank = function(formElement) {
	//Value must be null
	return $F(formElement) == "";
}

validator.prototype.is_numeric = function(formElement) {
	//Can be negative and have a decimal value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^-?((\d+(\.\d+)?)|(\.\d+))$/);
}

validator.prototype.is_numeric_positive = function(formElement) {
	//Must be positive and have a decimal value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^((\d+(\.\d+)?)|(\.\d+))$/);
}

validator.prototype.is_numeric_negative = function(formElement) {
	//Must be negative and have a decimal value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^-((\d+(\.\d+)?)|(\.\d+))$/);
}

validator.prototype.is_integer = function(formElement) {
	//Positive or negative whole number
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^-?\d+$/);
}

validator.prototype.is_integer_positive = function(formElement) {
	//Positive whole number
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^\d+$/);
}

validator.prototype.is_integer_negative = function(formElement) {
	//Negative whole number
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^-\d+$/);
}

validator.prototype.is_integer_natural = function(formElement) {
	//Natural whole number (i.e. Greater than 0)
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[1-9]\d*$/);
}

validator.prototype.is_alpha = function(formElement) {
	//Only alpha characters and underscore
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[a-z_]+$/i);
}

validator.prototype.is_alphanumeric = function(formElement) {
	//Match any alpha, number, or underscore characters
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^\w+$/);
}

validator.prototype.is_date = function(formElement) {
	//Match a date in mm/dd/yyyy format
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^(0?[1-9]|1[012])[- \/.](0?[1-9]|[12][0-9]|3[01])[- \/.](19|20)?[0-9]{2}$/);
}

validator.prototype.is_time = function(formElement) {
	//Match a date in hh:mm[:ss][ ][tt] format
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i);
}

validator.prototype.is_datetime = function(formElement) {
	//Match a datetime in mm/dd/yyyy hh:mm[:ss][ ][tt] format
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^(0?[1-9]|1[012])[- \/.](0?[1-9]|[12][0-9]|3[01])[- \/.](19|20)?[0-9]{2} [0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i);
}

validator.prototype.is_url = function(formElement) {
	//Match a url
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^(https?|ftp):\/\/([-A-Z0-9.]+)(\/[-A-Z0-9+&@#\/%=~_|!:,.;]*)?(\?[-A-Z0-9+&@#\/%=~_|!:,.;]*)?$/i);
}

validator.prototype.is_ssn = function(formElement) {
	//Match a ssn with or without hyphons
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^\d{3}-?\d{2}-?\d{4}$/i);
}

validator.prototype.is_email = function(formElement) {
	//Match a email address
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i);
}

validator.prototype.is_zip = function(formElement) {
	//Match a US zip code
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[0-9]{5}(?:-[0-9]{4})?$/);
}

validator.prototype.is_canadian_postal = function(formElement) {
	//Match a Canada postal code
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[ABCEGHJKLMNPRSTVXY][0-9][A-Z] [0-9][A-Z][0-9]$/);
}

validator.prototype.is_uk_postal = function(formElement) {
	//Match a UK postal code
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}$/);
}

validator.prototype.is_postal_zip = function(formElement) {
	//Match a us zip, canadian postal code, or uk postal code
	if($F(formElement) == "") {return true;};
	if(this.is_zip(formElement)) {return true;};
	if(this.is_canadian_postal(formElement)) {return true;};
	if(this.is_uk_postal(formElement)) {return true;};
	
	return false;
}

validator.prototype.is_phone = function(formElement) {
	//Match a North American Phone number
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^(1[-. ]?)?\(?[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}$/);
}

validator.prototype.is_isbn = function(formElement) {
	//Match an ISBN
	if($F(formElement) == "") {return true;};
	//For ISBN-10
	if($F(formElement).match(/^(?=.{13}$)\d{1,5}([- ])\d{1,7}\1\d{1,6}\1(\d|X)$/)) {return true;};
	if($F(formElement).match(/^(?=.{10}$)\d{9}(\d|X)$/)) {return true;};
	//For ISBN-13
	if($F(formElement).match(/^(?=.{17}$)\d{3}([- ])\d{1,5}\1\d{1,7}\1\d{1,6}\1(\d|X)$/)) {return true;};
	if($F(formElement).match(/^(?=.{14}$)\d{3}[- ]\d{9}(\d|X)$/)) {return true;};
	return false;
}

validator.prototype.is_money = function(formElement) {
	//Match a dollar value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/);
}

validator.prototype.is_money_positive = function(formElement) {
	//Match a dollar value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^\$?((\d+(\.\d{2})?)|(\.\d{2}))$/);
}

validator.prototype.is_money_negative = function(formElement) {
	//Match a dollar value
	if($F(formElement) == "") {return true;};
	return $F(formElement).match(/^((-\$?)|(\$?-))((\d+(\.\d{2})?)|(\.\d{2}))$/);
}

validator.prototype.is_byu_id = function(formElement) {
	//Match a BYU ID value
	if($F(formElement) == "") {return true;};
	if($F(formElement).match(/^\d{2}-\d{3}-\d{4}$/)) {return true;};
	if($F(formElement).match(/^\d{9}$/)) {return true;};
	return false;
}

validator.prototype.is_password = function(formElement) {
	//Match eight characters and at least one number
	valid = true;
	if(!$F(formElement).match(/\d/)) {
		valid = false;
	}
	if(!$F(formElement).match(/^.{8,}/)) {
		valid = false;
	}
	return valid;
}

validator.prototype.is_maxLength = function(formElement) {
	//Value must be the max length
	if($F(formElement) == "") {return true;};
	return $F(formElement).length == formElement.maxLength;
}

validator.prototype.is_minLength = function(formElement) {
	//Value must be at least the min length
	if($F(formElement) == "") {return true;};
	var minLength = this.parse_out_number(formElement, "minLength");
	if(minLength == -1) {return true;};
	return $F(formElement).length >= minLength;
}

validator.prototype.is_minOptionChoice = function(formElement) {	
	//total number of selected choices must be more than the minimum
	//This function is expecting classNames such as minOptionChoice10, minOptionChoice4, etc. We need to parse out the number after minOptionChoice to use it for testing
	var minOptionChoice = this.parse_out_number(formElement, "minOptionChoice");
	if(minOptionChoice == -1) {return true;};
	var totalOptionSelected = 0;
	//TODO: does this work properly in IE 6?
	var sameOptionName = document.getElementsByName(formElement.name);
	for(var i=0;i<sameOptionName.length;i++) {
		if(sameOptionName[i].checked==true) {
			totalOptionSelected++;
		}
	}
	return totalOptionSelected >= minOptionChoice;
}

validator.prototype.is_maxOptionChoice = function(formElement) {	
	//total number of selected choices must be less than the maximum
	var maxOptionChoice = this.parse_out_number(formElement, "maxOptionChoice");
	if(maxOptionChoice == -1) {return true;};
	var totalOptionSelected = 0;
	//TODO: does this work properly in IE 6?
	var sameOptionName = document.getElementsByName(formElement.name);
	for(var i=0;i<sameOptionName.length;i++) {
		if(sameOptionName[i].checked==true) {
			totalOptionSelected++;
		}
	}
	return totalOptionSelected <= maxOptionChoice;
}

validator.prototype.is_maxFloat = function(formElement) {
	//Value cannot have more digits then specified in maxFloat
	if($F(formElement) == "") {return true;};
	var maxFloat = this.parse_out_number(formElement, "maxFloat");
	if(maxFloat == -1) {return true;};
	var maxFloatPattern = new RegExp("^-?((\\d+(\\.\\d{0,"+maxFloat+"})?)|(\\.\\d{0,"+maxFloat+"}))$");
	return $F(formElement).match(maxFloatPattern);
}

validator.prototype.parse_out_number = function(formElement, className) {
	var number = 0;
	var indexStart = formElement.className.indexOf(" " + className);
	if(indexStart == -1) {
		indexStart = formElement.className.indexOf(className);
		if(indexStart != 0) {return -1;}
	}
	else {
		indexStart++;	
	}
	var indexEnd = formElement.className.indexOf(" ", indexStart);
	if(indexEnd == -1) {indexEnd = formElement.className.length;};
	number = formElement.className.substring(indexStart + className.length, indexEnd);
	if(isNaN(number)) {return -1;};
	
	return number;
}

validator.prototype.humanize_name = function(formElement) {
	var name = (formElement.id) ? formElement.id : formElement.name;
	name = name.replace(/([A-Z])/g, "_$1")
	nameParts = name.split("_");
	nameParts.shift();
	nameParts.shift();
	if(formElement.type.toLowerCase() == "radio" || formElement.type.toLowerCase()  == "checkbox") {nameParts.pop();}
	name = nameParts.join(" ");
	name = name.charAt(0).toUpperCase() + name.substring(1, name.length).toLowerCase();
	return name;
}