﻿/*******
*	
*	FormHandler: A class for validating forms.
* 	http://www.splashdust.net
*
* 	Require mootools, http://www.mootools.net/ 
*
*	Feel free to use this code in any way you like, commercial or non-commercial.
*	Any redistribution of this class should retain this header.
*/

var FormHandler = new Class({
/* v0.5 */

	Implements: Options,
	
	options: {
		required: 'required',
		output: 'FHOutput',
		identical: null,
		form: null,
		reminders: null,
		onValid: null,
		onInvalid: null,
		
		/* Default error messages */
		errorMsg: 'Please fill out all required fields.',
		textError: 'This field cannot be empty',
		selectError: 'Something needs to be selected',
		checkboxError: 'This checkbox need to be ticked',
		radioError: 'One option must be choosen',
		emailError: 'This email is not valid',
		numberError: 'This number is not valid',
		matchError: 'These fields must match',
		dateError: 'This date is not valid (use dd/mm/yyyy)',
		
		/* Default regexps */
		reEmail: /^[!-'*+=?{-~\/-9A-Z^-z-]+(\.[!-'*+={-~\/-9A-Z^-z-]+)*@[!#$%&'*+"\/=?^_`{|}~0-9A-Za-z-]+\.[!-'*+={-~\/-9A-Z^-z-]{2,}/,
		reDate: /^(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)\d\d/,
		reNumber: /^[0-9]+$/
	},
	
	initialize: function(options)
	{
		this.setOptions(options);
		this._output = $(this.options.output);
		this._form = $(this.options.form);
		this._form.onsubmit = this.testSubmit.bind(this);
		this._clsRequired = this.options.required;
		this._identical = this.options.identical;
		this._arrErrors = new Array;
		this._arrMsgs = new Array;
		this._errorMsg = this.options.errorMsg;
		this._reminderActive = false;
		
		// Register ovverride buttons that will force-submit the form without
		// checking the fields
		$$("input[type=submit]").each(function(btn,index)
		{
			if(btn.hasClass("nocheck")){
				btn.addEvent('click',this.forceSubmit.bind(this));
			}
		}.bind(this));
	},
	
	testSubmit: function()
	{	
		// Clean up previous run
		if(this.dirty == true) {
			this._arrErrors.each(function(objErr, index){
				objErr.elmInput.removeClass('error');
			});
			this._arrMsgs.each(function(elm, index){
				elm.destroy();
			});
			
			if(this._output) {this._output.set("text","");}
			this._arrErrors = new Array;
			this._arrMsgs = new Array;
		}
		
		if(this.hasErrors()) {
			if(this._output && !this._reminderActive) {
				this._output.set("text",this._errorMsg);
			}
			
			this._arrErrors.each(function(objErr, index){
				if(objErr.strMsg != null) {
					var errorText = new Element('span',{'class':'errortext'}).injectAfter(objErr.elmInput);
					errorText.set("text",objErr.strMsg);
					this._arrMsgs.include(errorText);
				}
			}.bind(this));
			
			this.dirty = true;
			if(this.options.onInvalid) {
				this.options.onInvalid();
			}
			return false;
		} else {
			if(this.options.onValid) {
				this.options.onValid();
			}
			return true;
		}
	},
	
	hasErrors: function()
	{
		this._arrErrors = new Array;
		var inputs = this._form.getElements("input,textarea,select,span");
		inputs.each(function(elm,index){
			
			// Default type is text if not otherwise specified
			if(elm.getProperty("type") == null && elm.get("tag") == "input")
				elm.set("type", "text");
			
			// Only check a field if it has the reuired class
			if((elm.hasClass(this._clsRequired) || this._clsRequired == "ALL") && elm.style.display != "none") {
				switch(elm.getProperty("type")){
				case "text":
				case "password":
					
					/* Check that the field is not empty */
					if(elm.value == "") {
						elm.addClass('error');
						this._arrErrors.include({elmInput:elm,strMsg:this.options.textError});
						
					} else if(elm.hasClass('email')) {
						
						/* This field has the email class, so check for valid email */
						if(!this.options.reEmail.test(elm.value)) {
							elm.addClass('error');
							this._arrErrors.include({elmInput:elm,strMsg:this.options.emailError});
						}
					} else if(elm.hasClass('date')) {
						
						/* This field has the email class, so check for valid email */
						if(!this.options.reDate.test(elm.value)) {
							elm.addClass('error');
							this._arrErrors.include({elmInput:elm,strMsg:this.options.dateError});
						}
					} else if(elm.hasClass('number')) {
						
						/* This field has the number class, so check for valid number */
						if(!this.options.reNumber.test(elm.value)) {
							elm.addClass('error');
							this._arrErrors.include({elmInput:elm,strMsg:this.options.numberError});
						}
					}
					
					break;
				
				case "radio":
					// All your radio buttons are belong to spans.
					// See the handling of spans below
				break;
				
				case "checkbox":
					
					if(!elm.checked) {
						elm.addClass('error');
						this._arrErrors.include({elmInput:elm,strMsg:this.options.checkboxError});
					}
					
					break;
				
				/* Handles stuff like selects and textareas */
				default:
				if(elm.get("tag") == "select") {
					//alert("Name: "+elm.name+" Value: '"+elm.value+"'")
					if(elm.value == "" || elm.value == "0") {
						elm.addClass('error');
						this._arrErrors.include({elmInput:elm,strMsg:this.options.selectError});
					}
				}
				if(elm.get("tag") == "textarea") {
					if(elm.value == "") {
						elm.addClass('error');
						this._arrErrors.include({elmInput:elm,strMsg:this.options.textError});
					}
				}
				
				// Required spans are used for groups of checkboxes or radiobuttons
				if(elm.get("tag") == "span") {
					
					if(elm.hasClass("radiobuttons") || elm.hasClass("checkboxes")) {
						this.foundChecked = false;
						elm.getElements("input").each(function(elmRb)
						{
							if(elmRb.checked)
								this.foundChecked = true;
						}.bind(this));
						
						if(!this.foundChecked) {
							elm.addClass('error');
							this._arrErrors.include({elmInput:elm,strMsg:this.options.radioError});
						}
						
						this.foundChecked = null;
					}
					
				}
				break;
				}
			}
			
			/* The section below handles format requirements on non-required fields */
			/* TODO: make required and non-required checks more seamless, instead of having two separate sections. */
			
			if(elm.hasClass('email') && !elm.hasClass('error') && elm.style.display != "none" && elm.value !='') {
				if(!this.options.reEmail.test(elm.value)) {
					elm.addClass('error');
					this._arrErrors.include({elmInput:elm,strMsg:this.options.emailError});
				}
			} else if(elm.hasClass('number') && !elm.hasClass('error') && elm.value !='') {
						
				/* Check number requirement on non-required fields */
				if(!this.options.reNumber.test(elm.value)) {
					elm.addClass('error');
					this._arrErrors.include({elmInput:elm,strMsg:this.options.numberError});
				}
			} else if(elm.hasClass('date') && elm.value !='') {
						
				/* Check date requirement on non-required fields */
				if(!this.options.reDate.test(elm.value)) {
					elm.addClass('error');
					this._arrErrors.include({elmInput:elm,strMsg:this.options.dateError});
				}
			}
			
		}.bind(this));
		
		if(this._identical) {
			this.tmpVal = $(this._identical[0]).value;
			this.tmpDiffer = false;
			
			this._identical.each(function(elm,index,val1) {
				if($(elm).value != this.tmpVal){
					this.tmpDiffer = true;
					$(elm).addClass('error');
					this._arrErrors.include({elmInput:$(elm),strMsg:this.options.matchError});
				}
			}.bind(this));
			
			if(this.tmpDiffer == true){
				
				$(this._identical[0]).addClass('error');
				this._arrErrors.include({elmInput:$(this._identical[0]),strMsg:this.options.matchError});
			
			}
			
			/* Clean up the mess */
			this.tmpVal = null;
			this.tmpDiffer = null;
		}
		
		/* Check if there are any reminders on the current field */
		if(this._arrErrors.length < 1 && this.options.reminders) {
			this.options.reminders.each(function(item, pos){
				if($(item.field).value == '') {
					this._reminderActive = true;
					this._output.set('text', item.reminder);
					this.options.reminders[pos] = null;
					$(item.field).addClass('error');
					this._arrErrors.include({elmInput:$(item.field),strMsg:null});
				}
			}.bind(this));
		}
		
		if(this._arrErrors.length < 1) {
			return false;
		} else {
			return true;
		}
	},
	
	/* Removes the onsubmit event, so that the checking will not occur */
	forceSubmit: function()
	{
		this._form.onsubmit = null;
	},
	
	validates: function() { return this.testSubmit() }
	
});


