// assume that validation is bound to the 'onBlur' event for each field (commented out now)
// and that we have a valid HTML form
//    -- no "floating" inputs (outside of form tags) ... there is a way to 'trap' these (see below)
//    -- all forms and inputs have name attributes set (and are unique)
//    -- to get the appropriate styling, markup must match the pattern established in the dealer prospectus forms (can also reference css pages for more info)

/*  more detailed breakdown of the localBinding configuration object:
 *  validate, onSuccess, and onFailure are now optional and fall back to the global object if set.
 *  if an input/select has one but not all set, the validation will auto pass but no errors are thrown... BE CAREFUL

    'form name' {
		            'field name' {
						             'validate' : {
						                 validation function used to validate this field. function should return true or false : { parameters }
						                 ...
						                 
						                 NOTE: would be a good idea to write wrappers for existing validation functions because parameters are stored on the field object.
						                 
									 'onSuccess' : {
									     callback for the function called when validation succeeds : { parameters for the function } 
									     ...
									 }
									     
									 'onFailure' : {
									     callback for the function called when validation fails : { parameters for the function }
									     ...
									 }
					             }
								
								...
								
					'onValidate': {
					    callback function called when the form is validated successfully.  These should be form specific. (optional) : { parameters }
					    ...
					    
					},
					    
					'onValidationFail' : {
					    callback function called when the form fails to validate.  Again, is page/form specific. (optional) : { parameters }
					    ...
					    
					},
					
					'onClear' : a function to call when the reset button is clicked (optional)
	            }
      ...
*/

/* group object breakdown
 * 
 * groups are validated by calling the validation for each individual field within the group
 * therefore, all groups MUST have individual field validation defined above.
 * if not set, the onSuccess and onFailure functions fall back to the individual fields
 * if set, the group onSuccess REPLACES onSuccess execution for the individual fields
 * pattern for onSuccess and onFailure follows the pattern above.
 
    
    'form name' : {
        'group name' : {
            'fields' : an array of field names which correspond to this group - *required*
            'onSuccess' : the function to call when validation passes (optional)
            'onFailure' : the function to call when validation fails (optional)
        }
    }

*/

/* global versions of the objects omit form names and simply list out field names */
/* examples are below */


/* ************************************************************************ */
/* GLOBAL: site specific definitions */
// eventually everything will be object oriented.

// custom callbacks
function postZip (zip) {
	// this function runs non-async, meaning it will hold up browser operation until it gets a return value
	// need to come up with an async version
	
	if (zip === "") {return false} else {
		
		var zipVerifyURL = "/MMNA/bingServiceAction.do";
		var params = {method : "validateZipCode", zipCode : $.trim(String(zip))};
		var returnVal = false;
		
		$.ajax({
			url: zipVerifyURL,
			data: params,
			success: function (result) {
			    if ($.trim(result) == "true") {
			    	returnVal = true;
			    }
		    },
			dataType: "text",
			async: false
		});
		
		return returnVal;
	}
	
	
	/*
	$.get(zipVerifyURL, params, function (result) {
		if ($.trim(result) == "true") {
			alert ('returning true');
			return true;
		}
		
		return false;		
	}, "text");
	*/
}



// new error messaging object
function errorItem () {
    this.name = '';
    this.value = '';
    this.valid = true;
    this.display = true;
}

function errorMessageStack () {
	this.globalMessage = 'There was an error processing your information. Please ensure that all required fields are filled out in their entirety:';
	this.items = new Array();
	
	/* don't really need this anymore
	this.init = function ( ) {
		// do a quick verify on the markup	
		var stackObj = $(this.form).find('.errorMessageStack');
		if (stackObj.length == 0) {
			// prepend the div
	        $(this.form).prepend("<div class='errorMessageStack' style='display:none'><span>" + globalErrorStackMessage
			    + "</span><ul></ul></div>");
		}
    },
    */
    
    this.display = function ( formName ) {
    	// display the error message stack
    };
	
    /*
	this.findItem : function (obj) {
		var el = false;
		if (obj.group != null) {
		    el = $($.map ($(obj.source).closest('form').data('groups')[obj.group].fields, function (objName, idx) {
		    	return 'input[name=' + objName + ']';	    	
		    }).join(', '));
		} else {
			el = $(obj.source);
		}	
		return el;
    },
    
	// helper function for genericValidate and its counterparts
	findErrorListItemName : function (obj) {
		if (obj.group != null) {
		    var liName = obj.group + '_' + $(obj.source).attr('name');
		} else {
			var liName = $(obj.source).attr('name');
		}
	    return liName;
	},
	
	*/
	
	this.addItem = function ( item ) {
		
	};
	
	this.removeItem = function ( item ) {
		var stackObj = $(this.form).find('.errorMessageStack');
		stackObj.find('li#' + liName).remove();
		if (stackObj.find('li').length == 0) {
			stackObj.hide();
		}		
	};
	
	this.buildName = function ( obj ) {
		return '';
	};

}
/* this function defines behavior for all error messaging across the board */
/* could use some major cleanup. Like separating out the error message stack stuff */
function genericValidate (obj) {
	var el = false;
	if (obj.group != null) {
	    var liName = obj.group + '_' + $(obj.source).attr('name');
	    el = $($.map ($(obj.source).closest('form').data('groups')[obj.group].fields, function (objName, idx) {
	    	return 'input[name=' + objName + ']';	    	
	    }).join(', '));
	} else {
		var liName = $(obj.source).attr('name');
		el = $(obj.source);
	}
		
	var id  = $(obj.source).attr('id');
	var lbl = $(obj.source).closest('ul').siblings('legend');
	if (lbl.length == 0) {
	    lbl = $("label[for="+id+"]");
	}
	
	if (obj.status == 'pass') {
		var emAdd = 'valid';
		var emRemove = 'error';
		var elAdd     = 'valid'; 
		var elRemove  = 'error'; 
		var lblAdd    = 'valid'; 
		var lblRemove = 'error'; 
	} else {
		var emAdd = 'error';
		var emRemove = 'valid';
		var elAdd     = 'error';
		var elRemove  = 'valid';
		var lblAdd    = 'error';
		var lblRemove = 'valid';
	}
	
	var theMessage = obj.message;
	if (!theMessage) {
		theMessage = '';
	};
	
	try {
		el.addClass(elAdd);
		el.removeClass(elRemove);
		lbl.addClass(lblAdd);
		lbl.removeClass(lblRemove);
		
		var theEM = $(obj.source).siblings('em').eq(0);
		//alert (theEM.length);
	    if (theEM.length) {
		    theEM.removeClass(emRemove);
		    theEM.addClass(emAdd);
	    } else {
	    	if ($(obj.source).attr('type') == 'radio') {
	    		if ($(obj.source).siblings('label').find('em').length <= 0) {
	    		    $(obj.source).siblings('label').append("<em class='" + emAdd + "' style='margin-bottom:-12px'></em>");
	    		    //$('input[name=' + $(obj.source).attr('name') + ']:checked').after("<em class='" + emAdd + "'></em>");
	    		}
	    	} else {
	    	    $(obj.source).after("<em class='" + emAdd + "'></em>");
	    	}
	    }
	} catch (e) {
	}
	
	// now for the form error message stack
	var stackObj = $(obj.source).closest('form').find('.errorMessageStack');
	if (stackObj.length == 0) {
		// create the required markup
	}
	
	if (obj.status == 'pass') {
		stackObj.find('li#' + liName).remove();
		if (stackObj.find('li').length == 0) {
			stackObj.hide();
		}
	} else {		
		stackObj.show();
		if (theMessage.length) {
			if (stackObj.find('li#' + liName).length == 0) {
				// if we are to establish an order for the error messages, this is where it would happen
		        stackObj.find('ul').append('<li id="' + liName + '">' + theMessage + '</li>');
			}
		}
	}
	
}

function obfuscateValidate (obj) {
	// another version of generic validate.
	// this one just hides any error classes and removes the item from the error message stack
	var el = false;
	if (obj.group != null) {
	    var liName = obj.group + '_' + $(obj.source).attr('name');
	    el = $($.map ($(obj.source).closest('form').data('groups')[obj.group].fields, function (objName, idx) {
	    	return 'input[name=' + objName + ']';	    	
	    }).join(', '));
	} else {
		var liName = $(obj.source).attr('name');
		el = $(obj.source);
	}
		
	var id  = $(obj.source).attr('id');
	var lbl = $(obj.source).closest('ul').siblings('legend');
	if (lbl.length == 0) {
	    lbl = $("label[for="+id+"]");
	}
	
	try {
		el.removeClass('valid');
		el.removeClass('error');
		lbl.removeClass('valid');
		lbl.removeClass('error');
		
		var theEM = $(obj.source).siblings('em').eq(0);
		//alert (theEM.length);
	    if (theEM.length) {
		    theEM.removeClass('valid');
		    theEM.removeClass('error');
	    }
	    
	} catch (e) {
	}
	
	// now for the form error message stack
	var stackObj = $(obj.source).closest('form').find('.errorMessageStack');
	if (stackObj.length == 0) {
		// create the required markup
	} else {
	
		stackObj.find('li#' + liName).remove();
		if (stackObj.find('li').length == 0) {
			stackObj.hide();
		}
	}
}

function radioValidateLocal (obj) {
    // local version of generic validate, specific to radio buttons
	// could be cleaned up a bit but so could the original version
	var el = false;
	if (obj.group != null) {
	    var liName = obj.group + '_' + $(obj.source).attr('name');
	    el = $($.map ($(obj.source).closest('form').data('groups')[obj.group].fields, function (objName, idx) {
	    	return 'input[name=' + objName + ']';	    	
	    }).join(', '));
	} else {
		var liName = $(obj.source).attr('name');
		el = $(obj.source);
	}
		
	var id  = $(obj.source).attr('id');
	var lbl = $(obj.source).closest('ul').siblings('legend');
	if (lbl.length == 0) {
	    lbl = $("label[for="+id+"]");
	}
	
	if (obj.status == 'pass') {
		var emAdd = 'valid';
		var emRemove = 'error';
		var elAdd     = 'valid'; 
		var elRemove  = 'error'; 
		var lblAdd    = 'valid'; 
		var lblRemove = 'error'; 
	} else {
		var emAdd = 'error';
		var emRemove = 'valid';
		var elAdd     = 'error';
		var elRemove  = 'valid';
		var lblAdd    = 'error';
		var lblRemove = 'valid';
	}
	
	var theMessage = obj.message;
	if (!theMessage) {
		theMessage = '';
	};
	
	try {
		el.addClass(elAdd);
		el.removeClass(elRemove);
		lbl.addClass(lblAdd);
		lbl.removeClass(lblRemove);
		
		var theEM = $('input[name=' + $(el).attr('name') + ']').eq(1).siblings('em').eq(0);
		//alert (theEM.length);
	    if (theEM.length) {
		    theEM.removeClass(emRemove);
		    theEM.addClass(emAdd);
	    } else {
	    	$('input[name=' + $(el).attr('name') + ']').eq(1).nextAll('label').after("<em class='" + emAdd + "'></em>");
	    }
	} catch (e) {}
	
	// now for the form error message stack
	var stackObj = $(obj.source).closest('form').find('.errorMessageStack');
	if (stackObj.length == 0) {
		// create the required markup
	}
	
	if (obj.status == 'pass') {
		stackObj.find('li#' + liName).remove();
		if (stackObj.find('li').length == 0) {
			stackObj.hide();
		}
	} else {		
		stackObj.show();
		if (theMessage.length) {
			if (stackObj.find('li#' + liName).length == 0) {
				// if we are to establish an order for the error messages, this is where it would happen
		        stackObj.find('ul').append('<li id="' + liName + '">' + theMessage + '</li>');
			}
		}
	}
			
}



// group pass / fail functions
// its possible to combine these
function groupValidatePass (obj) {
	var groupList = $(obj.source).closest('form').data('groups')[$(obj.source).data('group')].fields;
	var anObject = new Object();
	//alert (groupList.length);
	anObject.source = $(obj.source).closest('form').find('input[name=' + groupList[groupList.length-1] + '], select[name=' + groupList[groupList.length-1] + ']');
	if (obj.message) {
		anObject.message = obj.message;
	}
	anObject.status = 'pass';
	anObject.group = $(anObject.source).data('group');
	
	genericValidate (anObject);
}

function groupValidateFail (obj) {
	var groupList = $(obj.source).closest('form').data('groups')[$(obj.source).data('group')].fields;
	var anObject = new Object();
    //alert (groupList.length);
    anObject.source = $(obj.source).closest('form').find('input[name=' + groupList[groupList.length-1] + '], select[name=' + groupList[groupList.length-1] + ']');
    //alert ( 'name:' + $(anObject.source).attr('name') );
	if (obj.message) {
		anObject.message = obj.message;
	}
	anObject.status = 'fail';
	anObject.group = $(anObject.source).data('group');
	
    genericValidate (anObject);
}

function genericClear (obj) {
	var sourceObj = obj;
	if (obj.source) {
		sourceObj = obj.source;
	}
	
	var form = $(sourceObj).closest('form');
	form.find('.errorMessageStack').find('li').remove();
	form.find('.errorMessageStack').hide();
	
	$(sourceObj).closest('form').find('em, label, input, select, legend').each ( function () {
	    $(this).removeClass('error');
	    $(this).removeClass('valid');
	    //$(this).html('');
	});
	
}

/* global binding object breakdown:
 *     works the same as local binding, but there are no form names
 *     if a form field has a name attribute that matches an entry below, we use the global validation entry 
 *     local binding entries override global binding entries
 *     this object should be moved into a JSP so WebConstants can replace the field names
 */

var globalBinding = {
		'firstName' : {'validate' : {validateRegEx : {expression: /^[a-zA-Z\-\s]+$/}, validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the first name you entered is invalid. First names should be one word and not include numerals or symbols.'}}},
		'lastName' : {'validate' : {validateRegEx : {expression: /^[a-zA-Z\-\s]+$/}, validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the last name you entered is invalid. Last names should be one word and not include numerals or symbols.'}}},
		'email' : {'validate' : {validateMinLength : {'minLength' : 1}, validateRegEx: {expression: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z]{2,4})+$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the email you entered is not a valid address. Please check the address and try again.'}}},
/*		'addressOne' : {'validate' : {validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, you must include a street address.'}}},
		'address1' : {'validate' : {validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, you must include a street address.'}}},*/		'phoneAreaCode' : {'validate' : {validateRegEx: {expression: /^[2-9]{1}[0-8]{1}[0-9]{1}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the phone number you entered is not a valid phone number. Please check the number.'}}},
        'phoneAreaCode' : {'validate' : {validateRegEx: {expression: /^[2-9]{1}[0-8]{1}[0-9]{1}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the phone number you entered is not a valid phone number. Please check the number.'}}},
		'phonePrefix' : {'validate' : {validateRegEx: {expression: /^[2-9]{1}[0-9]{2}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the phone number you entered is not a valid phone number. Please check the number.'}}},
		'phoneSuffix' : {'validate' : {validateRegEx: {expression: /^[0-9]{4}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the phone number you entered is not a valid phone number. Please check the number.'}}},
        'secondaryPhoneAreaCode' : {'validate' : {validateRegEx: {expression: /^[2-9]{1}[0-8]{1}[0-9]{1}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the mobile number you entered is not a valid phone number. Please check the number.'}}},
        'secondaryPhonePrefix' : {'validate' : {validateRegEx: {expression: /^[2-9]{1}[0-9]{2}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the mobile number you entered is not a valid phone number. Please check the number.'}}},
        'secondaryPhoneSuffix' : {'validate' : {validateRegEx: {expression: /^[0-9]{4}$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the mobile number you entered is not a valid phone number. Please check the number.'}}},
//		'city' : {'validate' : {validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the city name you entered is not a valid city name. Please check the spelling and try again.'}}},
//		'state' : {'validate' : {validateDropdown : {}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, you must select a state.'}}},
//		'stateName' : {'validate' : {validateDropdown : {}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, you must select a state.'}}},
//		'zip' : {'validate' : {validateMinLength : {'minLength' : 5}, validateRegEx : {expression: /^[0-9]+(\-[0-9]+)*$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the zip code you entered is invalid. Please check the zip code and try again.'}}},
//		'zipCode' : {'validate' : {validateMinLength : {'minLength' : 5}, validateRegEx : {expression: /^[0-9]+(\-[0-9]+)*$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the zip code you entered is invalid. Please check the zip code and try again.'}}},
//		'zip' : {'validate' : {validateMinLength : {'minLength' : 5}, validateRegEx : {expression: /^[0-9]+(\-[0-9]+)*$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the zip code you entered is invalid. Please check the zip code and try again.'}}},
		'zipCode' : {'validate' : {validateCallback : {'callback' : "postZip"}, validateRegEx : {expression: /^[0-9]+(\-[0-9]+)*$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the zip code you entered is invalid. Please check the zip code and try again.'}}},
	    'username' : {'validate' : {validateMinLength : {'minLength' : 1}, validateRegEx: {expression: /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the user name you entered is invalid. Please check your user name and try again.'}}},
	    'password' : {'validate' : {validateMinLength : {'minLength' : 1}}, 'onSuccess' : {genericValidate: {}}, 'onFailure' : {genericValidate: {'message' : 'Sorry, the password you entered is invalid. Please check your password and try again.'}}},
	    'modelIdOfInterest' : { 'validate' : {validateDropdown : {}}, 'onSuccess' : {genericValidate : {}}, 'onFailure' : {genericValidate : {message : "Selection of a model of interest is required."}} }
};

var globalGroups = {
		'phone' : {
            fields: ['phoneAreaCode', 'phonePrefix', 'phoneSuffix'],
            onSuccess: {groupValidatePass : {message: ''}},
            onFailure: {groupValidateFail : {message: 'Sorry, the phone number you entered is not a valid phone number. Please check the number.'}}
        },
        'phoneSMS' : {
            fields: ['secondaryPhoneAreaCode', 'secondaryPhonePrefix', 'secondaryPhoneSuffix'],
            onSuccess: {groupValidatePass : {message: ''}},
            onFailure: {groupValidateFail : {message: 'Sorry, the mobile number you entered is not a valid phone number. Please check the number.'}}
        }
}


/* ************************************************************************ */
/* ENGINE: validation engine
 * fix: group functionality is not clean (duplicate code)
 *      some layout (HTML) requirements still defined in engine, not global code */

/* core validation functions */
function validateAlwaysTrue (obj) {
	return true;
}
function validateAlwaysFalse (obj) {
	return false;
}
function validateRegEx (obj) {
	if (String(obj.expression).length) {
		return ($(obj.source).val().search(obj.expression) >= 0) ? true : false;
	} else {
		return false;
	}
}
function validateMinLength (obj) {
    return ($(obj.source).val().length >= obj.minLength) ? true : false;
}
function valdiateMaxLength (obj) {
	return ($(obj.source).val().length <= obj.maxLength) ? true : false;
}
function validateDropdown (obj) {
	return (obj.source.selectedIndex > 0) ? true : false;
}
function mustMatch (obj) {
	return (obj.source.value == $(obj.source).closest('form').find('input[name='+obj.compareTo+'], select[name='+obj.compareTo+']').val()) ? true : false;
}
function validateMustMatch (obj) {
	return (mustMatch (obj));
}
function validateChecked (obj) {
	return (obj.source.checked == true) ? true : false;
}
function validateRadio (obj) {
	return ($('input[name=' + obj.source.name + ']:checked').length > 0);
}
function validateCallback (obj) {
	return (eval(obj.callback + '("' + $(obj.source).val() +  '")'));
}

/* validation engine begin */

$(document).ready ( function () {
//macro to validate multiple fields
	$(document).data('fullValidate', function (vObj) {
	if (vObj.length == 0) {
		return true;
	} else {		
		return eval (jQuery.map(vObj, function (obj, i) {
			try {
				var complete = true;
				jQuery.each ($(obj).data('validate'), function (func, params) {
					complete &= eval(func) ( jQuery.extend(params, {source: obj}) );
				});
				return complete;
			} catch (e) {
				// an object was passed that does not have a validate() set
				// alert ('whoops (' + vObj.name + '): ' + e);
				return true;
			}
		}).join(' && '));		
	}
	});
	
	$("form").each ( function () {
		var theForm = this;
		// BUILD
		// error message stack (div and ul)
		
		// grouped fields		
		try {
		jQuery.each(jQuery.extend(globalGroups, groupedFields[theForm.name]), function (groupName, inputFields) {
			// entering groupName
			if (!$(theForm).data('groups')) {
				// initializing groups object
				var groupsObject = new Object;
				$(theForm).data ('groups', groupsObject);
			}
			
			var groupData = $(theForm).data('groups');
			
			if (!groupData[groupName]) {
				// creating group
				groupData[groupName] = new Object;
				groupData[groupName].fields = new Array();
				
				jQuery.each (['onSuccess', 'onFailure'], function (idx, func) {
					try {						
						groupData[groupName][func] = new Object;
						jQuery.each (inputFields[func], function (callbackName, params) {							
							groupData[groupName][func][callbackName] = params;							
						});
					} catch (e) {}
				});

			}
			jQuery.each (inputFields.fields, function (i, inputName) {
				// adding field
				$(theForm).find('input[name=' + inputName + '], select[name=' + inputName + ']').data('group', groupName);
				groupData[groupName].fields.push(inputName);
			});
			//alert (groupName + ' has ' + $(theForm).data('groups')[groupName].fields.length);
			
		});
		} catch (e) { }
		
		// BUILD inputs
		$(theForm).find('input[name], select[name]').each ( function () {
			var curInput = this;
			
			// build out error message stack's LIs

			jQuery.each (['validate', 'onSuccess', 'onFailure'], function (idx, func) { 
				try {					
					if (localBinding[theForm.name][curInput.name][func]) {
						$(curInput).data (func, localBinding[theForm.name][curInput.name][func]);
					} else {
					    $(curInput).data (func, globalBinding[curInput.name][func]);	
					}				
				} catch (e) {
					try {
						$(curInput).data (func, globalBinding[curInput.name][func]);
					} catch (e) { }
				}
			});
			

		} );
		
		// submit
	    $(this).find ("input[type=submit], button.submitThisForm, input.submitThisForm, a.submitThisForm").each ( function() {
			// check for custom onValidate and onValidationFail definitions
	    	// BUILD
			try {
				$(this).data( 'onValidate', localBinding[$(this).closest('form').attr('name')].onValidate );
				$(this).data( 'onValidationFail', localBinding[$(this).closest('form').attr('name')].onValidationFail );
				// optional pre-validation function (ex: clear the message stack, disable submit button for ajax requests)
				if (localBinding[$(this).closest('form').attr('name')].preValidate) {
					$(this).data( 'preValidate', localBinding[$(this).closest('form').attr('name')].preValidate );
				}
				
			} catch (e) { }
			
			
			// BINDING
			$(this).click ( function () {
				// validate all fields
				var theForm = $(this).closest('form');
				var groups = new Array;
				var allValidateUngrouped = true;
				var allValidateGrouped = true;
				
				try {
					$(this).data('preValidate')();
				} catch (e) {
					// no preValidate function set
				}
				
				$(theForm).find('input[type=radio], input[type=text], input[type=password], input[type=checkbox], select[name]').each( function () {
					var curObj = this;
					var curObjArray = new Array (this);
					if ($(curObj).data('group')) {
						// validate groups later
						if (jQuery.inArray($(curObj).data('group'), groups) > -1) {
							//alert ($(curObj).attr('name') + ' is being skipped since ' + $(curObj).data('group') + ' is already listed');						    
						} else {
							//alert ($(curObj).attr('name') + ' is going into ' + $(curObj).data('group'));
					        groups.push ($(curObj).data('group'));
						}
					} else {
						// validate singular fields now
						try {
							if ( $(document).data('fullValidate')(curObjArray) ) {								
								jQuery.each ($(curObj).data('onSuccess'), function (callbackName, params) { 
							    	eval(callbackName)(jQuery.extend( params, {source: curObj, status: 'pass'} )) });
								
							} else {
								jQuery.each ($(curObj).data('onFailure'), function (callbackName, params) { 
							    	eval(callbackName)(jQuery.extend( params, {source: curObj, status: 'fail'} )) });
								
								allValidateUngrouped = false;
							}
						} catch (e) { /* enter here if no validate() was set */ }
					}
				});
				
				// now validate groups
				if (groups.length > 0) {
					allValidateGrouped = eval (jQuery.map (groups, function (currentGroup) {
						var firstObj = $(theForm).find('input[name=' + $(theForm).data('groups')[currentGroup].fields[0] + '], select[name=' + $(theForm).data('groups')[currentGroup].fields[0] + ']');
						//alert ($(firstObj).attr('name'));
						if ( $(document).data('fullValidate')( $(theForm).find(jQuery.map($(theForm).data('groups')[currentGroup].fields, function (gName) { 
							    return "input[name=" + gName + "], select[name=" + gName + "]";
							} ).join(", ")) ) ) {
							try {
								jQuery.each ($(theForm).data('groups')[currentGroup].onSuccess, function (callbackName, params) {
									eval(callbackName)(jQuery.extend( params, {source: firstObj, status: 'pass'} ));
								});
							} catch (e) {
								// fall back to first field message
								try {
								    jQuery.each ($(firstObj).data('onSuccess'), function (callbackName, params) {
								        eval(callbackName)(jQuery.extend( params, {source: firstObj, status: 'pass'} ) ) 
							        });
								} catch (e) {
									//alert ('we could not run onSuccess for ' + currentGroup + ' and tried to pass ' +$(firstObj).attr('name'));
								}
							}
							
							return true;
						} else {
							try {
								jQuery.each ( $(theForm).data('groups')[currentGroup].onFailure, function (callbackName, params) {
									eval(callbackName)( jQuery.extend ( params, {source: firstObj, status: 'fail'} ) );
								});
							} catch (e) {
								// fall back to first field message
								try {
								    jQuery.each ($(firstObj).data('onFailure'), function (callbackName, params) { 
									    eval(callbackName)(jQuery.extend( params, {source: firstObj, status: 'fail'} ) ); 
							        });
								} catch (e) {
									//alert ('we could not run onFailure for ' + currentGroup + ' and tried to pass ' +$(firstObj).attr('name'));
								}
							}							
							return false;
						} 
						
						return true;
					}).join(" && "));

				} else {
					allValidateGrouped = true;
				}
				
				if (allValidateGrouped && allValidateUngrouped) {
					try {
						jQuery.each ( $(this).data('onValidate') , function (callbackName, params) {
							eval(callbackName)( params );
						});
						return false;
					} catch (e) {
						// no custom onValidate defined. Either submit form or alert serialized form data for testing.
						//alert ("action: " + $(theForm).attr("action") + "\n\n\nPOSTDATA:" + $(theForm).serialize());
						//alert (e.message);
						theForm.submit();
					}
				} else {
					// put up additional error messages
					try {
						jQuery.each ( $(this).data('onValidationFail') , function (callbackName, params) {
							eval(callbackName)( params );
						});
					} catch (e) {
						//alert ('Please enter all required fields before submitting.'); // some default message
					}
				};
				
				return false;
			});
	    });
		
		
	    // define a form-specific 'onClear' function (usually to clear error messages)	    
	    try {
	        $(this).data('onClear', localBinding[theForm.name].onClear);
	    } catch (e) {}
	    
		$(this).find("input[type=reset], button.clearThisForm").click ( function () {
			// default behavior
			// this should be moved into a global function and removed from the engine {
			$(this).closest('form').find('em').each ( function () {
				$(this).removeClass('error');
				$(this).removeClass('valid');
				$(this).html('');
			});
			
			// this part can stay here maybe
			$(this).closest('form').find('input').val('');
			$(this).closest('form').find('select').each ( function () {
				this.selectedIndex = 0;
			});
			
			// }

			try {				
				$(this).closest('form').data('onClear')();
			} catch (e) {
				
			}
			return false;
		});
		
		$(this).find("input[type=reset], button.clearThisForm").mousedown ( function () {
			// this solves a particular browser event issue with <buttons>
			$(this).click();
			
			return false;
		});
		
	});
	
	// trap floating inputs
	/* commenting out, but leaving code in, so something can be done with 'floating' inputs later on
	try {
		$(":input").each ( function () {
			if (this.form == null) {
				//alert ('orphan\'d input with name: ' + this.name);
			}
		});
	} catch (e) {
		alert ("found an orphan.. that didn't get caught before...");
	}
	*/
});


