function addValidation(form, fnShowError, fnHideError, fnSubmit) {
	hasErrors = false;
	
	var validateEmail = function (email) {
		return $.trim(email).match(/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/);
	}
	
	var validatePostalCode = function(postalCode) {
		return $.trim(postalCode).match(/^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ])\ {0,1}(\d[ABCEGHJKLMNPRSTVWXYZ]\d$)/);
	}
	
	var showError = function(field, override) {
		if (fnShowError == null) {
			if (!$(field).hasClass('isDirty') && !override) return;
			var container = $(field).hasClass('phone') ? $(field).parent() : $(field);
			if ($('.errorMsg', container).length == 0) {
				var errorMsg = $('<span style="display:none" class="errorMsg">' + $(field).attr('data-msg') + '</span>').prependTo(container);
				errorMsg.show();
			}
			$('input, select', field).addClass('hasError');
		}
		else {
			fnShowError(field, override);
		}
	}
	
	var hideError = function(field) {
		if (fnHideError == null) {
			if ($(field).hasClass('phone')) {
				if ($(field).hasClass('different')) {
					if (!$($(field).attr('data-different-field')).parent().hasClass('required') && $($(field).attr('data-different-field')).val() == '' && $('input', field).val().match(/^\(*\d{3}\)*-{0,1}\s*\d{3}-{0,1}\s*\d{4}$/)) {
						$('.telephone .errorMsg').remove();
						$('.telephone input').removeClass('hasError');
					}
					else if ($($(field).attr('data-different-field')).parent().hasClass('required') && $($(field).attr('data-different-field')).val()!= '' && $($(field).attr('data-different-field')).val().match(/^\(*\d{3}\)*-{0,1}\s*\d{3}-{0,1}\s*\d{4}$/)) {
						$('.telephone .errorMsg').remove();
						$('.telephone input').removeClass('hasError');
					}
					else if ($('input', field).val().match(/^\(*\d{3}\)*-{0,1}\s*\d{3}-{0,1}\s*\d{4}$/) && $($(field).attr('data-different-field')).val()!= '' && $($(field).attr('data-different-field')).val().match(/^\(*\d{3}\)*-{0,1}\s*\d{3}-{0,1}\s*\d{4}$/) && $('input', field).val()!= $($(field).attr('data-different-field')).val()) {
						$('.telephone .errorMsg').remove();
						$('.telephone input').removeClass('hasError');
					}
				}
				else {
					//$('.errorMsg', $(field).parent()).remove();
					$(field).prev('.errorMsg').remove();
				}
			}
			else {
				$('.errorMsg', field).slideUp('slow', function() { 
					$(this).remove();
				});
				$('input, select', field).removeClass('hasError');
			}
		}
		else {
			fnHideError(field);
		}
	}
	
	var errorCorrected = function(field) {
		if ($(field).hasClass('phone')) {
			$('.errorMsg', $(field).parent()).addClass('corrected');
		}
		else {
			$('.errorMsg', field).addClass('corrected');
		}
		$('input', field).removeClass('hasError');
	}
		
	var checkField = function(field, override) {
		var input = $('input:enabled,select:enabled', field).eq(0);
		if (input.length == 0) return;
		var isEmpty = $.trim(input.val()) == '';		
		
		if ($(field).hasClass('postal-code') || $(field).hasClass('postal-or-zip')) {
			if ( $(input).val() != $(input).val().toUpperCase().replace(/\s/g, '') ){
				$(input).val($(input).val().toUpperCase().replace(/\s/g, ''));
			}
		}
		
		if (!isEmpty && $(field).hasClass('numeric')) {
			$(input).val($(input).val().replace(/\D+/g, ''));
		}
		
		if (!isEmpty && $(field).hasClass('email') && !validateEmail($(input).val())) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('postal-code') && !validatePostalCode($(input).val())) {
			hasErrors = true;
			showError(field, override);
			
		}
		else if (!isEmpty && $(field).hasClass('zip-code') && !$(input).val().match(/^[0-9]{5}(-[0-9]{4}){0,1}$/) ) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('postal-or-zip') && 
					(!validatePostalCode($(input).val()) && !$(input).val().match(/^[0-9]{5}(-[0-9]{4}){0,1}$/)) ) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('name') && !$(input).val().match(/^[A-Za-z'\u00C0-\u00FF]+[A-Za-z'\u00C0-\u00FF0-9-\. ]*$/)) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('alpha') && !$(input).val().match(/^[A-Za-z-\u00C0-\u00FF\. ]+/)) {
			hasErrors = true;
			showError(field, override);
		}
        else if (!isEmpty && $(field).hasClass('phone') && !$(input).val().match(/^\(*\d{3}\)*-{0,1}\s*\d{3}-{0,1}\s*\d{4}$/)) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('numeric') && !$(input).val().match(/^[0-9]+$/)) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('alpha-numeric') && !$(input).val().match(/^[A-Za-z-\u00C0-\u00FF0-9\. ]+$/)) {
			hasErrors = true;
			showError(field, override);
		}
		else if (!isEmpty && $(field).hasClass('password2') && $(input).val() != $('.password1 input', form).val()) {
			hasErrors = true;
			showError(field, override);
		}
		else if ($(field).hasClass('match') && $(input).val() != $($(field).attr('data-match-field')).val()) {
			hasErrors = true;
			showError(field, override);
		}
		else if ($(field).hasClass('different') && $(input).val() == $($(field).attr('data-different-field')).val()) {
			hasErrors = true;
			showError(field, override);
		}
		else if ($(field).hasClass('filter')) {
			var valueTaken = function() {
				var filterValues = $(field).attr('data-filter-values').split(',');
				var valueTaken = false;
				for (i = 0; i < filterValues.length; i++) {
					if (filterValues[i] == newValue) {
						valueTaken = true;
						break;
					}
				}
				return valueTaken;
			}
			var newValue = input.val();
			if (isEmpty) {
				hasErrors = true;
				showError(field, override);
			} else {
				if ($(field).attr('data-allow-values') == null) { // Address Book: adding new address
					if (valueTaken()) {
						hasErrors = true;
						showError(field, override);
					} else {
						hideError(field);
					}
				} else { // editing address
					var oldValue = $(field).attr('data-allow-values');
					if (oldValue!= newValue) {
						if (valueTaken()) {
							hasErrors = true;
							showError(field, override);
						} else {
							hideError(field);
						}
					} else {
						hideError(field);
					}
				}
			}
		}
		else {
			if (isEmpty && $(field).hasClass('required')) {
				hasErrors = true;
				showError(field, override);
			}
			else {
				if (!$(field).hasClass('lblPasswordOne')) {
					hideError(field);
				}
			}
		}
	}
	
	
	$('label:not(.lblPasswordOne)', form).each(function() {
		var field = $(this);
		//console.log('captured by form validator: ' + field.attr('class'));
		// This is the hack for IE
		if ($.browser.msie && $.browser.version <= 7) {
		  $('input[type=text]', this).eq(0).click(function() {
			$(this).blur();
			$(this).focus();
		  });
		}
		$('input[type=text],input[type=password],select', this).eq(0).change(function() {
			//console.log('this field changed: ' +field.attr('class'));
			if (field.hasClass('isDirty')) {
				/*$(this)
					.keyup(function() {
						checkField(field, false);
					})
					.blur(function() {
						checkField(field, false);
					});*/
				checkField(field, false);
			}
			else {
				field.addClass('isDirty');
				checkField(field, true);
			}
		});
	});
	
	$(form).submit(function() {
		hasErrors = false; 
		
		$('.phone input', form).each(function() {
			$(this).val($(this).val().replace(/\D+/g, ''));
		});
		
		$('label', form).each(function() {
			var field = $(this);
			$('input[type=text]:enabled,input[type=password]:not(.lblPasswordOne):enabled,select:enabled', this).eq(0).each(function() {

				checkField(field, true);
			});
			
		});
		
		$('input.lowerCase', form).each(function() {
			$(this).val($(this).val().toLowerCase());
		});
		
		if (hasErrors) {
			// don't scroll if form is in a popup
			if ($('body .window').length == 0) {
				var firstErrorOffset = $('.errorMsg').eq(0).offset();
				$.scrollTo(firstErrorOffset.top - 50, 500);
			}
			$('body').removeClass('busy');
			return false;
		}
		
		if (fnSubmit != null) {
			return fnSubmit(!hasErrors);
		}
	});
}
