/* formcheck - jQuery.formcheck.js
 *
 * Copyright (c) 2007 LisaraÃ«l (http://www.lisaserver.be) 
 * Licensed under the GPL (http://www.opensource.org/licenses/gpl-license.php) license.
 *
 * Version: 0.1
 *
 * Requires: jQuery 1.2+
 *
 * Compatibility: (untested now, will work with...)
 *		WinXP: Firefox, Opera, MSIE 7
 *		MacOSX: Firefox, Safari, Opera
 *
 * WARNING : This is a "in-dev" version.
 * 
 * ATTENTION ! : il y a une erreur dans les expreg des dates qui permettent des chiffres abÃ©rrants pour les mois et jours (genre 99). Ã  vÃ©rifier.
 *
 */
 
jQuery.fn.formcheck = function(settings) {
	settings = jQuery.extend({
		disabled : false, // désactive l'envoi du formulaire si tout n'est pas correct
		bgColor_empty : "#FFF",
		bgColor_error : "#000",
		bgColor_accept : "#8FCE7E",
		ind_required : '<img title="Ce champ est obligatoire" alt="Ce champ est obligatoire" src="img/control_rewind.png" />',
		ind_req_empty : '<img title="Ce champ obligatoire ne peut être vide !" alt="Ce champ obligatoire ne peut être vide !" src="img/exclamation.png" />',
		ind_false_format : '<img title="Le format de ce champ n\'a pas été respecté !" alt="Le format de ce champ n\'a pas été respecté !" src="img/exclamation.png" />',
		ind_accept : '<img title="Ce champ est correctement formatté." alt="Ce champ est correctement formatté." src="img/accept.png" />',
		//help_box : '<div class="message_box"><h3><img alt="" src="img/help.png" /> &Agrave; propos de ce champ</h3></div>',
		helping : true
	}, settings);
	settings.inputs = [];
	
	// actions directes à  l'appel de la fonction, on check les input et on ajoute les indicateurs adéquats
	jQuery(this).find(":input").each(function(ind) {
		if( typeof( jQuery(this).attr("xmlns:check") ) != "undefined" ) {
			checking( jQuery(this), ind );
		}
	});
	
	if( settings.inputs.length && settings.disabled ) {
		form_check();
	}
	
	// parsage et (si besoin) préparation de chaque input
	function checking(elt, ind) {
		// crÃ©ation des paramÃ¨tres importants
		if( !jQuery(elt).old_color && !jQuery(elt).chk_params ) {
			settings.inputs[ind] = {
				old_color : jQuery(elt).css("backgroundColor"),
				chk_params : jQuery(elt).attr("xmlns:check").split("|"),
				accepted : false,
				elt_ref : jQuery(elt)
			};
			// ajout de l'indicateur de "champs requis"
			if( settings.inputs[ind].chk_params[0] == "required" ) {
				required_ind = jQuery(settings.ind_required).addClass("indicator");
				if( settings.helping ) {
					jQuery(required_ind).click(function() {
						display_help_box(elt, ind);
					});
				}
				jQuery(elt).css("backgroundColor", settings.bgColor_empty).after( required_ind );
				settings.inputs[ind].accepted = false;
			} else {
				settings.inputs[ind].accepted = true;
			}
		}
		
		// analyse de la chaine de format
		switch( settings.inputs[ind].chk_params[1] ) {
			case "url": // url, ne prends pas d'attribut
				jQuery(elt).blur(function() {
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( settings.RegExp.url.test( jQuery(elt).val() ) ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			case "email": // email, ne prends pas d'attribut
				jQuery(elt).blur(function() {
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( settings.RegExp.email.test( jQuery(elt).val() ) ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			case "int": // entier, prends un attribut optionnel range (format x;y)
				jQuery(elt).blur(function() {
					var number = parseInt(jQuery(elt).val(),10);
					var limits = settings.inputs[ind].chk_params[2].split(";");
					var beginLimit = parseInt(limits[0],10);
					var endLimit = parseInt(limits[1],10);
					var inLimits = true;
					if( !isNaN( beginLimit ) && !isNaN( endLimit ) ) {
						inLimits = (beginLimit <= number && number <= endLimit);
					}
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( !isNaN(number) && inLimits ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			case "float": // flottant, prends un attribut range (format x;y)
				jQuery(elt).blur(function() {
					var number = parseFloat(jQuery(elt).val().replace(",", "."),10);
					var limits = settings.inputs[ind].chk_params[2].split(";");
					var beginLimit = parseFloat(limits[0],10);
					var endLimit = parseFloat(limits[1],10);
					var inLimits = true;
					if( !isNaN( beginLimit ) && !isNaN( endLimit ) ) {
						inLimits = (beginLimit <= number && number <= endLimit);
					}
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( !isNaN(number) && inLimits ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			case "text": // texte, prends un attribut range (format x;y)
				jQuery(elt).blur(function() {
					var length = parseInt(jQuery(elt).val().length,10);
					var limits = settings.inputs[ind].chk_params[2].split(";");
					var beginLimit = parseInt(limits[0],10);
					var endLimit = parseInt(limits[1],10);
					var inLimits = true;
					if( !isNaN( beginLimit ) && !isNaN( endLimit ) ) {
						inLimits = (beginLimit <= length && length <= endLimit);
					}
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( !isNaN(length) && inLimits ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			case "date": // date, prends un attribut format
				jQuery(elt).blur(function() {
					var test;
					switch( settings.inputs[ind].chk_params[2] ) {
						case "datetime":
							test = settings.RegExp.datetime.test( jQuery(elt).val() )
							break;
						case "YYYY-mm-dd":
							test = settings.RegExp.date.test( jQuery(elt).val() )
							break;
						case "dd/mm/YYYY":
							test = settings.RegExp.textDate.test( jQuery(elt).val() )
							break;
					}
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else if( test || (settings.inputs[ind].chk_params[0] == "optional" && !test) ) {
						change_state("accept", elt, ind);
					} else {
						change_state("error", elt, ind);
					}
				});
				break;
			default:
				jQuery(elt).blur(function() {
					if( settings.inputs[ind].chk_params[0] == "required" && jQuery(elt).val() == "" ) {
						change_state("empty", elt, ind);
					} else if( settings.inputs[ind].chk_params[0] == "optional" && jQuery(elt).val() == "" ) {
						change_state("optional", elt, ind);
					} else {
						change_state("accept", elt, ind);
					}
				});
				break;
		}
		
		
	} // checking
	
	function change_state(state, elt, ind) {
		switch(state){
			case "empty":
				jQuery(elt).css("backgroundColor", settings.bgColor_empty ).next("img.indicator").remove().end().after( jQuery(settings.ind_req_empty).addClass("indicator") );
				settings.inputs[ind].accepted = false;
				break;
			case "error":
				jQuery(elt).css("backgroundColor", settings.bgColor_error ).next("img.indicator").remove().end().after( jQuery(settings.ind_false_format).addClass("indicator") );
				settings.inputs[ind].accepted = false;
				break;
			case "accept":
				jQuery(elt).css("backgroundColor", settings.bgColor_accept ).next("img.indicator").remove().end().after( jQuery(settings.ind_accept).addClass("indicator") );
				settings.inputs[ind].accepted = true;
				break;
			case "optional":
			default:
				jQuery(elt).css("backgroundColor", settings.inputs[ind].old_color ).next("img.indicator").remove();
				settings.inputs[ind].accepted = true;
				break;
		}
		if( settings.disabled ) {
			form_check();
		}
	} // change_state
	
	function form_check() {
		var passed = true;
		jQuery(settings.inputs).each(function() {
			passed = (jQuery(this)[0].accepted) ? passed : false;
		});
		if( passed ) {
			jQuery("input[type=submit]").removeAttr("disabled");
		} else {
			jQuery("input[type=submit]").attr("disabled", "disabled");
		}
	} // form_check
	
	function display_help_box(elt, ind) {
		box_content = "";
		if( settings.inputs[ind].chk_params[0] == "required" ) {
			box_content += '<p>Ce champ est obligatoire.</p>';
		} else {
			box_content += '<p>Ce champ est facultatif.</p>';
		}
		switch( settings.inputs[ind].chk_params[1] ) {
			case "email":
				box_content += '<p>Ce champ, s\'il est rempli, doit respecter le format email : <strong>nom@site.com</strong>.</p>';
				break;
		}
		if( jQuery(elt).parents("div").children(".message_box").size() ) {
			jQuery(elt).parents("div").children(".message_box").slideToggle();
		} else {
			jQuery(elt).parents("div").append( jQuery(settings.help_box).hide().append( jQuery(box_content) ) ).children(".message_box").slideToggle();
		}
	} // display_help_box
	
	// expressions rÃ©guliÃ¨res, on les met Ã  la fin pour Ã©viter les saloperies avec les colorations syntaxiques.
	settings.RegExp = {
		email : /^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.([a-z]){2,4})$/,
		date : /^([2][0]|[1][9])\d{2}\-([0]\d|[1][0-2])\-([0-2]\d|[3][0-1])$/, // YYYY mm dd
		textDate : /^(([0-2]\d|[3][0-1])\/([0]\d|[1][0-2])\/([2][0]|[1][9])\d{2})$/, // dd/mm/YYYY
		datetime : /^([2][0]|[1][9])\d{2}\-([0]\d|[1][0-2])\-([0-2]\d|[3][0-1]) [0-2][0-9]:[0-5][0-9]$/, // mysql datetime
		// url regex by Scott Gonzalez: http://projects.scottsplayground.com/iri/
		url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
	};
};


/* 	----- Utilisation -----
	Chaque input du formulaire doit avoir un attribut xmlns:check, qui doit contenir certaines informations concernant la validation
	Chaque information est sÃ©parÃ©e par un pipe ( | ).
	La premiÃ¨re info concerne le caractÃ¨re obligatoire du champ de formulaire.
	Il peut avoir deux valeurs : "required" ou "optional". Lorsqu'il est sur required, le champ est vÃ©rifiÃ© et retourne une erreur si vide, avant mÃªme de tester son contenu. Cette vÃ©rification s'effectue lorsque le champ perd le focus.
	L'info suivante contient le type de donnÃ©e, et la troisiÃ¨me info contient des paramÃ¨tres pour affiner ce type.
	Voici ces types:
		- text : vÃ©rifie la longueur d'une donnÃ©e textuelle, exprimÃ©e dans les options, telle que suit
						<input xmlns:check="required|text|0;25" />
					signifie que le contenu du champ doit Ãªtre un texte dont la longueur se situe entre 0 et 25 caractÃ¨res.

		- email : ne prends pas d'option, vÃ©rifie la validitÃ© de la syntaxe d'un email entrÃ©

		- int et float : prennent la mÃªme option que pour les textes, afin de vÃ©rifier si le nombre donnÃ© se situe entre les valeurs limites

		- date : prends une option qui dÃ©fini le format de date Ã  vÃ©rifier, et peut Ãªtre le suivant
			- datetime pour un datetime mysql
			- dd/mm/YYYY pour une date Ã  ce format
			- YYYY mm dd pour une date Ã  ce format
*/
