/* * * * * * * * * * * * * * * * * * * * * * *
 *      CARENZA validator script file        *
 *                                           *
 *       (c) Copyright 2010 - CARENZA        *
 *         http://www.carenza.nl/            *
 *                                           *
 * * * * * * * * * * * * * * * * * * * * * * *
 *
 *
 * Validator functions. Use these functions to add client-side validation to forms.
 *
 * Requires javascript and jQuery to run.
 *
 * Use: call the validate function when you want to validate. For example, when a form is submitted.
 * This function will return true if all tests pass.
 *
 * Each element can have multiple tests. A test is added to the validation of an element by adding css classes to it.
 *
 * Currently, these tests are implemented:
 *   lengthn	: test the value to have at least n characters. For example: length3 to test fields for a minumum length of 3.
 *   email	: test the value to be a valid e-mail address. The validator is stricter than the specification, but most of the e-mail providers accept only these e-mail addresses
 *   integer	: test the value to be a positive or negative integer (3 or -12).
 *   decimal	: test the value to be a positive or negative decimal (4.8 or -12,42). Note: both commas and dots are allowed as decimal separator.
 *   postalCode	: test the value to be a postal code from the BeNeLux (4 numbers, possibly a space and possibly two letters)
 *   regex_<regex> : test the value to be a regular expression. Note: spaces can not be used in the pattern, use '[SPACE]' instead
 *
 * Furthermore, tests can be conditional. A condition can be creating by adding a class of the following form:
 *   condition_{name}_{if|ifnot}_{test}
 * This will create a condition with name {name}. The value of the condition depends on whether {test} passes or fails on the field, and on whether 'if' or 'ifnot' is used, with 'ifnot' being the negation of 'if'.
 * Conditional tests can then be created using the following class:
 *   ifcondition_{name}_{test}
 * This field will then be tested if and only if a condition with name {name} is found and evaluates to true.
 */

/**
 * validate all elements selected with selector. On success, adds passCss to element class. On fail, adds failCss to element class.
 * @param {string} selector CSS3/jQuery selector of all elements to be validated
 * @param {string} passCss the css class to add if an element passes all tests
 * @param {string} failCss the css class to add if an element fails all tests
 * @return {boolean} true iff all tests pass
 */
function validate(selector, passCss, failCss){
        if($("#questionField").attr("value") == "Opmerking"){
            $("#questionField").attr("value", "");
        }
        if($("#name").attr("value") == "Naam"){
            $("#name").attr("value", "");
        }
        if($("#email").attr("value") == "E-mailadres"){
            $("#email").attr("value", "");
        }
        if($("#phone").attr("value") == "Telefoonnummer"){
            $("#phone").attr("value", "");
        }

	var success = true;
	var items = $(selector);
	var conditionals = getConditionals(items);
	items.each(function() {
		var $item = $(this);
		success = validateItemCss($item, conditionals, passCss, failCss) && success;
    });

	var revalidateFunction = function() {
		conditionals = getConditionals(items);
		items.each(function() {
			var $item = $(this);
			validateItemCss($item, conditionals, passCss, failCss);
	    });
	};

	if(!success) {
		items.unbind('keyup');
		items.bind('keyup', throttle(revalidateFunction, 100));
	}
    return success;
}

/**
 * Get all conditionals
 * @param items the items to check for conditionals
 * @returns {Array} the found conditionals
 */
function getConditionals(items) {
	var conditionals = new Array();
	var n = 0;
	items.each(function() {
		var classes = this.className.split(' ');

		for (var i = 0; i < classes.length; i++) {
			var cssClass = classes[i];

			var regex = /^condition_([a-zA-Z]+)_(if|ifnot)_(.*)$/;
			if(regex.test(cssClass)) {
				var conditionName = RegExp.$1;
				var condition = RegExp.$2;
				var testValue = RegExp.$3;

				var itemValidates = validateItem(testValue, $(this).val());
				var conditionTrue = (condition == 'if') ? itemValidates : !itemValidates;

				conditionals[n] = {name: conditionName, value: conditionTrue};
				n++;
			}
		}
	});
	return conditionals;
}

/**
 * Validate an alement. On success, adds passCss to element class. On fail, adds failCss to element class
 * @param {Object} $item the jQuery object representing the element to validate
 * @param {array} conditionals the conditionals that have been identified
 * @param {string} passCss the css class that has to be added when the element passes all tests
 * @param {string} failCss the css class that has to be added when the element failes one of the tests
 * @return {boolean} true iff all tests pass
 */
function validateItemCss($item, conditionals, passCss, failCss) {
	var success = validateItem($item.attr('class'), $item.val(), conditionals);

	if(success) {
		$item.removeClass(failCss).addClass(passCss);
	} else {
		$item.removeClass(passCss).addClass(failCss);
	}

	return success;
}

/**
 * Splits all classes and loops through them, looking for classes that determine how to validate the value
 * @param {string} classString the string containing all classes
 * @param {string} value the value to validate
 * @param {array} conditionals the conditionals
 * @return {boolean} true iff value passes all tests
 */
function validateItem(classString, value, conditionals){
	var classes = classString.split(' ');
	var valid = true;

	for (var i = 0; i < classes.length; i++) {
		var cssClass = classes[i];

		// test for condition
		var regex = /^ifcondition_([a-zA-Z]+)_(.*)$/;
		if(regex.test(cssClass)) {
			// extract parameters
			var conditionName = RegExp.$1;
			var testString = RegExp.$2;

			// look up condition, test and if true, validate
			for(var i = 0; i < conditionals.length; i++) {
				var cond = conditionals[i];
				if(cond.name == conditionName && cond.value) {
					return validateItem(testString, value, conditionals);
				}
			}
		}

		// email
		if (cssClass == 'email') {
			valid = valid && validateItemEmail(value);
		}

		// length
		var regex = /^length([1-9][0-9]*)$/;
		if(regex.test(cssClass)) {
			var maxLength = parseInt(RegExp.$1);
			valid = valid && validateItemLength(value, maxLength);
		}

		// integer
		if(cssClass == 'integer') {
			valid = valid && validateItemInteger(value);
		}

		// decimal
		if(cssClass == 'decimal') {
			valid = valid && validateItemDecimal(value);
		}

		// postal code
		if(cssClass == 'postalCode') {
			valid = valid && validatePostalCode(value);
		}

		// regular expression
		var regex = /^regex_(.*)$/;
		if(regex.test(cssClass)) {
			var expression = RegExp.$1;
			valid = valid && validateItemRegularExpression(value, expression);
		}
	}

    return valid;
}

/**
 * Test whether value is an e-mail address
 * @param {string} value the value to test
 * @return {boolean} true iff value appears to be a valid e-mail address
 */
function validateItemEmail(value) {
	var regex = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
	return regex.test(value);
}

/**
 * Tests whether value has at least maxLength characters
 * @param {Object} value the value to test
 * @param {Object} minLength the minimum number of characters
 * @return {boolean} true iff value has at least minlength characters
 */
function validateItemLength(value, minLength) {
	return (value.length >= minLength);
}

/**
 * Tests whether value is an integer (positive or negative)
 * @param {string} value the value to test
 * @return {boolean} true iff value is an integer
 */
function validateItemInteger(value) {
	var regex = /^[+-]?[0-9]+$/;
	return regex.test(value);
}

/**
 * Tests whether value is a decimal (positive or negative)
 * @param {string} value the value to test
 * @return {boolean} true iff value is a decimal (at least one digit in the integral part, and, if there is a decimal dot or comma, at lest one decimal digit)
 */
function validateItemDecimal(value) {
	var regex = /^[+-]?[0-9]+([.,][0-9]+)?$/;
	return regex.test(value);
}

/**
 * Test whether value is a postal code from the BeNeLux (1234 or 1234AB). Upper- and lower case are allowed, as well as a space between the numbers and letters
 * @param {string} value the value to test
 * @return {boolean} true iff value is a postal code from the BeNeLux.
 */
function validatePostalCode(value){
	var regex = /^[0-9]{4}( )?([a-zA-Z]{2})?$/;
	return regex.test(value);
}

/**
 * Validate the value using the given regular expression
 * @param value the value to test
 * @param expression the expression to test with
 * @return {boolean} true iff value matches expression
 */
function validateItemRegularExpression(value, expression) {
	var regex = new RegExp(expression.replace('[SPACE]', ' '));
	return regex.test(value);
}


/**
 * Throttle calls, so that a function will be called (delay) ms after the last call
 * @param fn the function to call
 * @param delay the delay after the last call
 * @returns {Function} A throttling function
 * @author Remy Sharp
 */
function throttle(fn, delay) {
	var timer = null;
	return function () {
		var context = this, args = arguments;
		clearTimeout(timer);
		timer = setTimeout(function () {
			fn.apply(context, args);
		}, delay);
	};
}



