/*	name			: ClassBehaviours, the javascript framework based on class-name parsing	update			: 20081124	author			: Maurice van Creij	dependencies	: jquery.classbehaviours.js	info			: http://www.classbehaviours.com/

    This file is part of jQuery.classBehaviours.
    
    ClassBehaviours is a javascript framework based on class-name parsing.
    Copyright (C) 2008  Maurice van Creij

    ClassBehaviours is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ClassBehaviours is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with ClassBehaviours. If not, see http://www.gnu.org/licenses/gpl.html.*/

	// create the jQuery object if it doesn't already exist
	if(typeof(jQuery)=='undefined') jQuery = function(){};
	
	// create the root classbehaviours object if it doesn't already exist
	if(typeof(jQuery.classBehaviours)=='undefined') jQuery.classBehaviours = function(){};
	
	// create the handlers child object if it doesn't already exist
	if(typeof(jQuery.classBehaviours.handlers)=='undefined') jQuery.classBehaviours.handlers = function(){}

	// blinks
	jQuery.classBehaviours.handlers.validateForm = {
		// properties
		name: 'validateForm',
		// methods
		start: function(node){
			// for all elements of this form
			allNodes = node.getElementsByTagName('*');
			for(var a=0; a<allNodes.length; a++){
				// if this element is a row add the default class name
				if(allNodes[a].nodeName=='TR') allNodes[a].className += (allNodes[a].className.indexOf('passive')<0) ? ' passive' : '' ;
				// if this element is a submit button, hijack the form submit
				if(allNodes[a].type=='submit') allNodes[a].onclick  = this.submitted;
				// if this element is marked with the test_* class
				if(allNodes[a].className.indexOf('test_')>-1){
					// test properties
					testName = jQuery.classBehaviours.utilities.getClassParameter(allNodes[a], 'test', null);
					testApplied = null;
					// for all tests, check if this is the name and make it apply itsself to the form element
					for(b in this.tests) if(this.tests[b].name == testName) testApplied = this.tests[b].apply(allNodes[a]);
					// if it wasn't found assume it's the id to a regExp, apply the custom regexp handler
					if(testApplied==null) testApplied = this.custom.apply(allNodes[a]);
				}
			}
		},
		summary: function(node){
			// get all elements of this form
			rootNode = jQuery.classBehaviours.utilities.rootNode(node, 'FIELDSET');
			allRows = rootNode.getElementsByTagName('TR');
			// for all rows, if the row is an error, remember its label
			errorMessage = '';
			for(var a=0; a<allRows.length; a++){
				if(allRows[a].className.indexOf('error')>-1){
					errorLabels = allRows[a].getElementsByTagName('LABEL');
					if(errorLabels.length>0) errorMessage += '<li>'+ errorLabels[0].innerHTML +'</li>';
				}
			}
			// get the summary field
			validationSummaries = jQuery.classBehaviours.utilities.getElementsByClassName('summary', rootNode);
			if(validationSummaries.length>0){
				// construct the summary message
			    validationTextId = jQuery.classBehaviours.utilities.getClassParameter(validationSummaries[0], 'error', '');
				validationText = document.getElementById(validationTextId).value;
				validationSummaries[0].innerHTML = validationText.replace('{labels}', '<ul>' + errorMessage + '</ul>');
				validationSummaries[0].style.display = (errorMessage!='') ? 'block' : 'none' ;
			}
			// return if the form passed the tests
			return (errorMessage=='');
		},
		update: function(node, status){
			// is this a mandatory field
			mandatoryId = jQuery.classBehaviours.utilities.getClassParameter(node, 'required', 'no');
			mandatory = (mandatoryId!='yes' && mandatoryId!='no') ? document.getElementById(mandatoryId).checked : (mandatoryId=='yes');
			// is this an empty field
			empty = (node.value == '' || node.value == node.title);
			// adjust the status for non-mandatory empty field
			ignore = (!mandatory && empty);
			// get the root of this row
			rootNode = jQuery.classBehaviours.utilities.rootNode(node, 'TR');
			// adjust the classname to reflect the validation status
			rootNode.className = (ignore) ?
				rootNode.className.replace('success', 'passive').replace('error', 'passive') : 
				(status) ?
					rootNode.className.replace('passive', 'success').replace('error', 'success') :
					rootNode.className.replace('passive', 'error').replace('success', 'error');
			// pass back the status
			return (status || ignore);
		},
		ajaxWait: function(waitStatus, waitNode, waitError){
			waitNode.innerHTML = (waitStatus>-1) ? 'Loading: ' + Math.round(waitStatus * 100) + '%' : 'Error: ' + waitError ;
			waitNode.style.display = 'block';
		},
		ajaxReplace: function(replaceXml, replaceNode, replaceText){
			replaceNode.innerHTML = replaceText.split('<root>')[1].split('</root>')[0];
		},
		// events
		submitted: function(that){
			var node = (typeof(this.nodeName)=='undefined') ? that : this ;
			var vfo = jQuery.classBehaviours.handlers.validateForm;
			var queryString = '';
			// get all elements of this form
			rootNode = jQuery.classBehaviours.utilities.rootNode(node, 'FIELDSET');
			allNodes = rootNode.getElementsByTagName('*');
			// for all elements
			for(var a=0; a<allNodes.length; a++){
				// assume the test failed
				testValidated = false;
				// if the element is marked with the test_* class, trigger its onchange event
				if(allNodes[a].className.indexOf('test_')>-1) if(typeof(allNodes[a].onchange)!='undefined') testValidated = allNodes[a].onchange();
				// build the query string from the name and value pairs
				if(allNodes[a].nodeName=='INPUT' || allNodes[a].nodeName=='SELECT' || allNodes[a].nodeName=='TEXTAREA')
					queryString += (allNodes[a].checked || (allNodes[a].type!='radio' && allNodes[a].type!='checkbox')) ? allNodes[a].name + '=' + escape(allNodes[a].value) + '&' : '' ;
			}
			// update the summary
			summaryResult = vfo.summary(rootNode);
			// if the form passed the tests, submit the form normaly, or via AJAX
			replyUrlId = jQuery.classBehaviours.utilities.getClassParameter(rootNode, 'url', null);
			replyTargetId = jQuery.classBehaviours.utilities.getClassParameter(rootNode, 'target', null);
			if(replyUrlId!=null){
/* TODO:
	- submit via iframe
	- fix radios
	- fix checkboxes
	- fix dependencies
*/
				// submit the form using ajax
				ajaxUrl = (document.getElementById(replyUrlId).nodeName=='FORM') ? document.getElementById(replyUrlId).action : document.getElementById(replyUrlId).value;
				ajaxTarget = document.getElementById(replyTargetId);
				if(summaryResult) jQuery.classBehaviours.ajax.addRequest(ajaxUrl + queryString, vfo.ajaxReplace, vfo.ajaxWait, null, ajaxTarget);
// debug('ajax', summaryResult, ajaxUrl, queryString);
				// cancel the real submit
				return false;
			}else{
// debug('non ajax', summaryResult);
				return summaryResult;
			}
		}
	}
		jQuery.classBehaviours.handlers.validateForm.tests = {
			email: {
				name: 'email',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^[\w\.\-\,\+]+@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/)!=null);
				}
			},
			nlPhone: {
				name: 'nlPhone',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/(^\+[0-9]{2}|^\+[0-9]{2}\(0\)|^\(\+[0-9]{2}\)\(0\)|^00[0-9]{2}|^0)([0-9]{9}$|[0-9\-\s]{10}$)/)!=null);
				}
			},
			nlPostal: {
				name: 'nlPostal',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^[0-9]{4}\s{0,1}[a-zA-Z]{2}$/)!=null);
				}
			},
			isoDate: {
				name: 'isoDate',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^\d{4}\-\d{1,2}\-\d{1,2}$/)!=null);
				}
			},
			numeric: {
				name: 'numeric',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^[0-9]+$/)!=null);
				}
			},
			currency: {
				name: 'currency',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^[0-9]+(\.[0-9]{1,2})?$/)!=null);
				}
			},
			alphaNumeric: {
				name: 'alphaNumeric',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value.match(/^[a-zA-Z0-9]/)!=null);
				}
			},
			nlBank: {
				name: 'nlBank',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					inputValue = node.value;
					if (inputValue.length!=9){
						return vfo.update(node, false);
					}else{
						numericTotal = 0;
						for (a=0; a<inputValue.length; a++) numericTotal += inputValue.substr(a, 1) * (9 - a);
						return vfo.update(node, numericTotal%11==0);
					}
				}
			},
			anyRadio: {
				name: 'anyRadio',
				apply: function(node){
					// if this node contains inputs
					allInputs = node.getElementsByTagName('INPUT');
					if(allInputs.length==0) allInputs = new Array(node);
					// apply the event handler and classname to all child nodes
					for(var a=0; a<allInputs.length; a++){
						if(allInputs[a].type=='radio'){
							allInputs[a].className = node.className;
							allInputs[a].onchange = this.validate;
						}
					}
					// report success
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					// default validation
					anyChecked = false;
					// get all inputs with this name
					allInputs = document.getElementsByTagName('input');
					// for all inputs, if the input has the same name, if the input is checked set the validator to true
					for(var a=0; a<allInputs.length; a++) if(allInputs[a].name == node.name && allInputs[a].type == 'radio') if(allInputs[a].checked) anyChecked = true;
					// return the result
					return vfo.update(node, anyChecked);
				}
			},
			anyCheckbox: {
				name: 'anyCheckbox',
				apply: function(node){
					// if this node contains inputs
					allInputs = node.getElementsByTagName('INPUT');
					if(allInputs.length==0) allInputs = new Array(node);
					// apply the event handler and classname to all child nodes
					for(var a=0; a<allInputs.length; a++){
						if(allInputs[a].type=='checkbox'){
							allInputs[a].className = node.className;
							allInputs[a].onchange = this.validate;
						}
					}
					// report success
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					// default validation
					anyChecked = false;
					// get all inputs from the parentnode
					parentNode = jQuery.classBehaviours.utilities.rootNode(node, 'TR');
					allInputs = parentNode.getElementsByTagName('input');
					// for all inputs, if this checkbox is checked remember is
					for(var a=0; a<allInputs.length; a++) if(allInputs[a].checked) anyChecked = true;
					// return the result
					return vfo.update(node, anyChecked);
				}
			},
			notEmpty: {
				name: 'notEmpty',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					return vfo.update(node, node.value!="");
				}
			},
			password: {
				name: 'password',
				apply: function(node){
					node.onchange = this.validate;
					return true;
				},
				validate: function(that){
					var node = (typeof(this.nodeName)=='undefined') ? that : this ;
					var vfo = jQuery.classBehaviours.handlers.validateForm;
					// default validatie
					wasRepeated = false;
					// get the target field to compare
					repeatedId = jQuery.classBehaviours.utilities.getClassParameter(node, "clone", null);
					// if the id is valid
					if(repeatedId!=null){
						repeatedValue = document.getElementById(repeatedId).value;
						wasRepeated = (repeatedValue==node.value && repeatedValue!='' && node.value!='');
					}
					// report back
					return vfo.update(node, wasRepeated);
				}
			}
		}
		jQuery.classBehaviours.handlers.validateForm.custom = {
			apply: function(node){
				node.onchange = this.validate;
				return true;
			},
			validate: function(that){
				var node = (typeof(this.nodeName)=='undefined') ? that : this ;
				var vfo = jQuery.classBehaviours.handlers.validateForm;
				customRegId = jQuery.classBehaviours.utilities.getClassParameter(node, 'test', null);
				customRegString = document.getElementById(customRegId).value;
				customRegExp = new RegExp(customRegString);
				return vfo.update(node, node.value.match(customRegExp)!=null);
			}
		}	

	// add this addon to the jQuery object
	if(typeof(jQuery.fn)!='undefined'){
		// extend jQuery with this method
		jQuery.fn.validateForm = function(){
			return this.each(
				function(){
					jQuery.classBehaviours.handlers.validateForm.start(this);
				}
			);
		};
		// if the main parser isn't present
		if(typeof(jQuery.classBehaviours.parser)=='undefined'){
			// set the event handler for this jQuery method
			$(document).ready(
				function(){
					$(".validateForm").validateForm();
				}
			);
		}
	}


