/*
** CLASS CheckForm
** Créée par Mickael B. Alexandre
** Site 	:	http://www.imagika.fr
** Blog	:	http://mickaelbertrand.imagika.fr
**
** Version 0.5 datée du 12-02-2009
**	Release 1 daté du 27-01-2009
**
** Licence ATC MIW - Promotion 2008-2009
** http://www.gap.univ-mrs.fr/miw/
** 
** Classe sous LICENCE X-11
**
** @données membres : ######################################################
**	Configuration : ---------------------------------------------------------------------------------------------------------------
**		- msgElement 		@String		balise créée pour l'affichage du message
**		- debug			@Boolean 		mode debug
**		- authorizeKeyControl	@Boolean 		mode qui empeche de taper les touche qui ne correpondrait pas à la regex
**		- validateColor		@String		Code couleur si champs valide   (ex #0FF ou #139AFF ou transparent)
**		- errorColor			@String		Code couleur si champs invalide (ex #0FF ou #139AFF ou transparent)
**		- msgAttributes		@String		Contient tous les attributs pour la personnalisation du message d'erreur
**
**	Class : -------------------------------------------------------------------------------------------------------------------------
**		- formulaire	@Element 		élément qui pointe sur le formulaire par défaut document
**		- c			@Array		contient les identifiants des champs à controler( id ou name)
**		- value		@Array		contient la valeur du champs à controler
**		- t			@Array		contient les types des champs à controler (text, textArea, select, 
**                                                        			textBox, textRadio, button etc ..)
**		- r			@Array		contient les règles de validation des champs à contrôler ( required,                 
**									requiredLenFix,alpha,alphaLenFix, alphanum,alphanumLenfix,
**									number, email, phone, url, digit, nodigit, verif , expReg etc )
**		- m			@Array		contient les messages à afficher si les champs à contrôler ne 
**									respectent pas les règles définies
**		- p			@Array		contient éventuellement des paramètres exemple:min[min,max]
**		- l			@Array		contient éventuellement des champs liés entre eux
**		- messageError	@Array		contenant tous les erreurs trouvées dans le formulaire indice n étant le numéro de chaque règle
**		- reg			@Array		contient toutes les regex possibles 
**		- authorized	@Array		contient tous les caractères possibles
**
** @fonctions :  ############################################################
**	MODIFIEURS : ---------------------------------------------------------------------------------------------------------------
**		- setValue
**		- setFormulaire
**		- setAuthorizeKeyControl
**		- setDebug
**		- setValidateColor
**		- setErrorColor
**		- setMsgElement
**
**	ACCESSEURS : ---------------------------------------------------------------------------------------------------------------
**		- getMessageError
** 
**	EVENT FUNCTIONS : ---------------------------------------------------------------------------------------------------------------
**		- traiteKeyCode
**		- traitePress
**		- traiteSubmit
**
**	OTHERS FUNCTIONS : ---------------------------------------------------------------------------------------------------------------
**		- addReg
**		- initialise
**		- validateReg
**		- execute
**
**	MESSAGE FUNCTIONS : ---------------------------------------------------------------------------------------------------------------
**		- hiddeMsg
**		- afficheMsg
**		- colorize
**		- showError
**
**	SYSTEM FUNCTIONS : ---------------------------------------------------------------------------------------------------------------
**		- $
**		- insertAfter
**		- addEventListener
**		- removeEventListener
**		- bind
** 
** 
*/

//@fid 	string (id du formulaire cible)
function CheckForm(fid) {
	
	this.fid = fid;
	this.setFormulaire(fid);
	
	//données membres
	this.formulaire 	= document;
	this.messageError 	= [];
	this.value			= [];
	this.c				= [];
	this.t				= [];
	this.r				= [];
	this.m				= [];
	this.p				= [];
	this.l				= [];
	this.ol				= [];
	this.e				= [];
}//__construct

CheckForm.prototype = {
	
	/* ***Paramétrage de la classe*** */
	
	debug				: false,		//true debug activé, false desactivé
	authorizeKeyControl	: false,			//true controle à la frappe activé, false controle à la frappe desactivé
	
	idMsg				: null,			//null 	= fonctionnement par défaut de la classe on affiche le message en dessous du champs
										//'id-conteneur' 	= id-conteneur (ou n'importe quel string) est l'id du champs dans lequel seront affiché TOUS les messages
	
	defaultLinkOperator	: '==',			// l'opérateur utilisé par défaut pour lier deux champs
	regSeparator		: ':',
	
	
	validateColor	: '#FFFFFF',
	errorColor		: '#FECC77',
	msgElement		: 'li',
	
	//on peut ajouter autant d'attribut de balise qu'on le souhaite (title,src,title,...)
	msgAttributes	: {
				
				'style' : 'color=#000;padding:2px;font-size:0.9em;',
				 
				 'class' : 'checkform-error'
				
				//example pour afficher une image
				 //src:'./image.png'
				//il faut aussi mettre le msgElement à img plus haut dans le code
				 //msgElement		: 'img',
	},

	//données membres
	
	formulaire 		: document,    
	messageError 	: [],
	value			: [],
	c				: [],
	t				: [],
	r				: [],
	m				: [],
	p				: [],
	l				: [],
	ol				: [],
	e				: [],
	
	authorized: {
		alpha : /^[a-z ._\-]$/i,
		alphanum : /^[a-z0-9 ._\-]$/i,
		digitSign : /^[\-+0-9]$/,
		digit : /^[0-9]$/,
		nodigit : /^[^0-9]$/,
		number : /^[\d\.]$/,
		email : /^[a-zA-Z0-9\._%@\-]$/i,
		phone : /^[\d\s\+\.\-]$/,
		url : /^[a-z0-9\-\.\/_]$/i,
		date : /^[0-9\s\. ]$/,
		hexaColor : /#[0-9a-fA-F]$/
	},
	
	
	reg:{
		required : /[^.*]/,
		alpha : /^[a-z ._\-]+$/i,
		alphanum : /^[a-z0-9 ._\-]+$/i,
		digitSign : /^[\-+]?[0-9]+$/,
		digit : /^[0-9]+$/i,
		nodigit : /^[^0-9]+$/,
		number : /^[\-+]?\d*\.?\d+$/,
		email : /^[a-z0-9._%\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$/i,
		phone : /^[\d\s ().\-]+$/,
		url : /^(http|https):\/\/[a-z0-9\-\.\/_]+\.[a-z]{2,3}$/i,
		date : /^[0-9]{1,2}.[0-9]{1,2}.[0-9]{2,4}$/,
		hexaColor : /#[0-9a-fA-F]{6}/,
		notEmpty : /^[^0\s]+$/
		//le cas custom est créé dans addReg ou non en fonction des désir de l'utilisateur
	},
	
	prevKeyCode : '',
	
	keyCodeValues : {
		32 : ' ',  9  : '\t', 13 : '\n', 48 : 'à', 49 : '&', 50 : 'é',
		51 : '"',  52 : "'",  53 : '(',  54 : '-',  5 : 'è', 56 : '_',
		57 : 'ç',
		
		96 : '0',  97 : '1',   98 : '2', 99 : '3', 100 : '4', 101 : '5',
		102 : '6',103 : '7', 104 : '8',	105 : '9',
		
		65 : 'a', 66 : 'b',  67 : 'c',
		68 : 'd', 69 : 'e',  70 : 'f',
		71 : 'g', 72 : 'h',  73 : 'i',
		74 : 'j', 75 : 'k',  76 : 'l',
		77 : 'm', 78 : 'n',  79 : 'o',
		80 : 'p', 81 : 'q',  82 : 'r',
		83 : 's', 84 : 't',  85 : 'u',
		86 : 'v', 87 : 'w',  88 : 'x',
		89 : 'y', 90 : 'z',
		
		111 : '/',109 : '-',  107 : '+', 106 : '*',
		186 : ';',187 : '=',  188 : ',', 189 : '-', 191 : '/', 
		220 : '\\', 219 : ')',222 : '\'',221 : '^', 192 : '`', 190 : ';'
		
	},
	
	
	/* ***** SET FUNCTIONS ****** */
	
	
	//@elem	element 	(élément dont il faut récupérer la valeur)
	//@n		number	(numéro de la règle sur lequel doit porter cet ajout)
	//desc 	initialise dans le tableau value la valeur du champs
	setValue : function (elem, n) {
		//prend la valeur du champs
		if (elem.value !== '') {
			this.value[n] = elem.value;
		}
		else {
			this.value[n] = '';
		}
	},
	
	//@fid	string (id of the form)
	//desc	initialise l'élément formulaire ave le fid
	setFormulaire: function (fid) { 
		
		this.formulaire = document.getElementById(fid);
		if(this.formulaire.nodeName == 'FORM') {
			this.addEventListener(this.formulaire,"submit",this.bind(this,this.traiteSubmit),false);
		}
		else if(this.formulaire.nodeName == 'INPUT') {
			this.addEventListener(this.formulaire,"click",this.bind(this,this.traiteSubmit),false);
		}
	},
	
	//@bool	boolean (true = press control activated)
	//desc	initialise the control mode
	setAuthorizeKeyControl: function (bool) { 
		this.authorizeKeyControl = bool;
	},
	
	//@bool	boolean (true = debug activated)
	//desc	initialise the debug mode
	setDebug: function (bool) { 
		this.debug = bool;
	},
	
	//@color	string (hexadecimal color (ex : #fff or #03A93A)
	//desc	initialise the validateColor
	setValidateColor: function (color) { 
		this.validateColor = color;
	},
	
	//@color	string (hexadecimal color (ex : #fff or #03A93A)
	//desc	initialise the errorColor
	setErrorColor: function (color) { 
		this.errorColor = color;
	},
	
	//@value	string (valeur du message)
	//desc 	initialise la donnée membre msgElement
	setMsgElement: function (value) {
		this.msgElement = value;
	},
	
	//@value	string (id du conteneur)
	//desc 	initialise la donnée membre idMsg
	setIdMsg: function (value) {
		this.idMsg = value;
	},
	
	
	/* ***** GET FUNCTIONS ****** */

	//desc 	retourne le message d'erreur courant
	getMessageError: function() { 
		return this.messageError;
	},
	
	
	/* ***** EVENT FUNCTIONS ****** */
	
	traiteKeyCode : function (keyCode,loc_e) {
		var return_value = null;
		
		if(typeof(this.keyCodeValues[keyCode]) !== 'undefined' && ! loc_e.shiftKey && ! loc_e.altKey) {
			return_value = this.keyCodeValues[keyCode];
		}
		else if(typeof(this.keyCodeValues[keyCode]) !== 'undefined' && (this.prevKeyCode == 16 || loc_e.shiftKey)) {
			switch(keyCode) {
				case 48 :
					return_value = '0';
					break;
				case 219 :
					return_value = '°';
					break;
				case 107 :
					return_value = '+';
					break;
				case 190 :
					return_value = '.';
					break;
				default :
					return_value = (keyCode % 48);
			}
		}
		else if(typeof(this.keyCodeValues[keyCode]) !== 'undefined' && (this.prevKeyCode == 18 || loc_e.altKey)) {
			switch(keyCode) {
				case 48 :
					return_value = '@';
					break;
				case 49 :
					return_value = '^';
					break;
				case 50 :
					return_value = '~';
					break;
				case 55 :
					return_value = '`';
					break;
				case 52 :
					return_value = '|';
					break;
				case 53 :
					return_value = '[';
					break;
				case 54 :
					return_value = '{';
					break;
				case 51 :
					return_value = '#';
					break;
				case 56 :
					return_value = '\\';
					break;
				case 219 :
					return_value = ']';
					break;
				case 107 :
					return_value = '}';
					break;
				default :
					return_value =  this.keyCodeValues[keyCode];
			}
		}
		else if (typeof(this.keyCodeValues[keyCode]) !== 'undefined') {
			return_value =  this.keyCodeValues[keyCode];
		}
		
		this.prevKeyCode = keyCode;
		return return_value;
	},
	
	traitePress : function (n,e) {
		var loc_e = e || window.event;
		//on autorise les flèches directionelles, le tab et le retour en arrière
		if(loc_e.keyCode == 9 || loc_e.keyCode == 8 || loc_e.keyCode == 37 || loc_e.keyCode == 38 
			|| loc_e.keyCode == 39 || loc_e.keyCode == 40)
			return;
		
		var loc_reg = this.parseReg(this.r[n]);
		for(i in loc_reg) {
			if ( typeof(this.authorized[loc_reg[i]]) !== 'undefined')
			{
				var keyValue = this.traiteKeyCode(loc_e.keyCode, loc_e);
				if (this.authorized[loc_reg[i]].test(keyValue) === false) {
					this.messageError[n] = this.m[n];
					break;
				}
				else {
					delete this.messageError[n];
				}
			}
		}
		
		if (typeof(this.messageError[n]) !== 'undefined') { // && (this.messageError.join('')).length !== 0) {
			if (loc_e.preventDefault) {
			   loc_e.preventDefault();  // FF
			}
			loc_e.returnValue = false;  // IE
			return false;
		}
		
		this.validateReg(n);
	},
	
	traiteSubmit: function (e) {
		this.validateReg();
		if (this.messageError !== '' && (this.messageError.join('')).length !== 0) {
			var loc_e = e || window.event;
			if (loc_e.preventDefault) {
			   loc_e.preventDefault();  // FF
			}
			loc_e.returnValue = false;  // IE
			
			return false;
		}
	},
	
	/* ***** EXECUTION FUNCTIONS ****** */
	
	
	//@chp		identifiant du champ à contrôler (id ou name)
	//@typ		type du champ à contrôler
	//@reg		règle à appliquer sur le champ
	//@event		event qui déclenche la validation
	//@mes		message à afficher si la règle n'est pas vérifiée
	//@par		paramètre facultatif pouvant préciser la règle supplémentaire. (taille de la value)
	//@custom_reg	paramètre facultatif pouvant préciser la règex à utiliser.
	//@liens		paramètre facultatif pouvant préciser les champs liés
	//desc		remplit les 5 tableaux de description d'un champ
	//			permet d'enregistrer une nouvelle règle
	addReg: function (chp, typ, reg, event, mes, par, custom_reg, liens, operateur_liens) {
		if ( this.$(chp) !== null )
		{
			//on passe à initialise le tableau d'argument de la fonction
			this.initialise(arguments);
			var att = this.c.length-1;
			
			//on ajoute l'écouteur sur tous les radio ou checkbox portant ce name
			if (typeof(typ) != 'undefined')
			{	
				if (typ == 'radio' || typ == 'checkbox'){
					var tab_element = document.getElementsByName(chp);
					for(var i in tab_element) {
						this.addEventListener(tab_element[i],event,this.bind(this,this.validateReg,[att]),false);
					}
				}
				else 
					this.addEventListener(this.$(chp),event,this.bind(this,this.validateReg,[att]),false);
			}
				
			if ( this.authorizeKeyControl === true && this.existAuthorizedReg(att))
			{
				this.addEventListener(this.$(chp),'keydown',this.bind(this,this.traitePress,[att]),false);
			}
			
			
		}
		else { this.showError('Développeur : Attention l\'élément id = "' + chp + '" n\'existe pas.\n'
							+'l\'élément name = "' + chp + '" n\'existe pas non plus.');
		}
							
	},

	//desc 	cette fonction récupère un tableau de paramètre donné par addReg
	initialise: function() {
		this.c.push(arguments['0']['0']);
		this.t.push(arguments['0']['1']);
		this.r.push(arguments['0']['2']);
		this.e.push(arguments['0']['3']);
		this.m.push(arguments['0']['4']);
		this.p.push(arguments['0']['5']);
		//on ajoute à reg une regex custom
		var custom = arguments['0']['6'];
		if ( custom !== '' && custom != 'undefined' && typeof(custom) != 'undefined'){
			this.reg.custom = custom;
		}
		//on ajoute à un l les liens
		this.l.push((arguments['0']['7'] != 'undefined') ? arguments['0']['7'] : '') ;
		//on ajoute l'opérateur pour le lien courant
		this.ol.push((arguments['0']['8'] != 'undefined') ? arguments['0']['8'] : '') ;
		
	},

	//@param		le numéro des règles à valider
	//desc		Valide une ou plusieurs règles
	validateReg: function() {
		if (arguments.length !== 0)
		{
			for (var i = 0 , sz = arguments.length; i < sz ; ++i) {
				this.execute(arguments[i]);
			}
		}
		else
		{
			for (var n in this.t) {
				if(n != 'undefined') {
					this.execute(n);
				}
			}
		}
	},
	//@n 	number
	//desc 	execute les opération sur un champs
	execute: function (n) {
		if(isNaN(n)) { return; }
		
		this.setValue(this.$(this.c[n]),n);
		
		var loc_reg = this.parseReg(this.r[n]);
		
		var isError = false;
		
		if (loc_reg.lenght !== 0)
		{	
			for(i in loc_reg) {
				if(typeof(loc_reg[i]) == 'string') {
					if ( ! this.reg[loc_reg[i]].test(this.value[n])) {
						isError = true; break;
					}
				}
			}
		}
		if (typeof(this.p[n]) != 'undefined')
		{
			var length = (typeof(this.value[n]) == 'undefined') ? 0 : this.value[n].length;
			if ( length < this.p[n]['0'] || length > this.p[n]['1'] ) {
				isError = true;
			}
		}
		if (typeof(this.t[n]) != 'undefined')
		{	
			var tab_element = document.getElementsByName(this.c[n]);
			var nb_checked = 0;
			
			for (var i in tab_element) {
				//debug ?
				if(typeof(tab_element[i]) !== 'object'){ continue; }
				if(tab_element[i].checked === true) {
					++nb_checked;
				}
			}
			
			if (this.t[n] == 'radio'){
				if ( this.r[n] == 'required') {
					if (nb_checked < 1) {
						isError = true;
					}
				}
				//this hack may be possible but... useless ?
				if ( nb_checked > 1) {
					isError = true;
				}
			}
			else if (this.t[n] == 'checkbox')
			{
				if (typeof(this.p[n]) != 'undefined')
				{
					if ( nb_checked < this.p[n]['0'] || nb_checked > this.p[n]['1'] )
						isError = true;
				}
			}
		}
		
		if (typeof(this.l[n]) != 'undefined')
		{
			for ( var i in this.l[n] )
			{
				var operator = (typeof(this.ol[n]) !== 'undefined') ? this.ol[n] : this.defaultLinkOperator;
				
				if ( ! eval("'"+this.value[n]+"' "+operator+" '"+this.$(this.l[n][i]).value+"'")) {
					isError = true;
				}
			}
		}
			
		if (isError)
		{
			this.messageError[n] = this.m[n];
			this.afficheMsg(n);
		}
		else { this.hiddeMsg(n); delete this.messageError[n];}
	},
	
	
	/* ***** DISPLAY FUNCTIONS ****** */
	
	
	//desc 	supprime le node relatif au message
	hiddeMsg: function (n) {
		//destruction du message
		if (document.getElementById(this.fid+'-msg-error-'+n) === null) { return false; }
		
		var node = document.getElementById(this.fid+'-msg-error-'+n);
		node.parentNode.removeChild(node);
		
		//colorisation du champ
		this.colorize(n,false);
	},
	
	//desc 	créée le node relatif au message
	afficheMsg: function (n) {
		//construction du message
		this.hiddeMsg(n);
		var new_node = document.createElement(this.msgElement);
		new_node.id = this.fid+'-msg-error-'+n;
		var txt = this.m[n];
		var new_txt = document.createTextNode(txt);
		new_node.appendChild(new_txt);
		
		for (var i in this.msgAttributes) {
			if(i != 'undefined') {
				new_node.setAttribute(i,this.msgAttributes[i]);
			}
		}
		
		if(this.idMsg !== null) {
			var cur_node = this.$(this.idMsg);
			cur_node.appendChild(new_node,cur_node);
		}
		else {
			var cur_node = this.$(this.c[n]);
			this.insertAfter(new_node,cur_node);
		}
		
		//colorisation du champ
		this.colorize(n,true);
	},
	
	//desc 	colorise le champ en fonction de l'état
	colorize: function (n, state) {
		if (state) {
			this.$(this.c[n]).style.backgroundColor = this.errorColor;
		}
		else {
			this.$(this.c[n]).style.backgroundColor = this.validateColor;
		}
	},
	
	
	//desc 	montre des erreur de code en alert si le debuggage est actif
	showError: function (msg) {
		if (this.debug) { alert(msg); }
	},
	
	
	/* ***** SYSTEM FUNCTIONS ****** */
	
	//@reg 	string
	//desc 	retourne un tableau contenant (la ou) les différentes règles demandées
	existAuthorizedReg : function (n) {
		var loc_reg = this.parseReg(this.r[n]);
		
		if (loc_reg.lenght !== 0)
		{	
			for(i in loc_reg) {
				if ( typeof(this.authorized[loc_reg[i]]) !== 'undefined' ) { return true;}
			}
		}
		
		return false;
	},
	
	//@reg 	string
	//desc 	retourne un tableau contenant (la ou) les différentes règles demandées
	parseReg : function (reg) {
		return reg.split(this.regSeparator);
	},

		
	//@elem	string (id ou name d'un élément)
	//desc	cette fonction n'est pas l'équivalent de la classe de celle de prototype mais permet de récupérer un élement 
	//		par son id si il existe sinon par son name le premier name = elem du document
	$ 	: function (elem) {
		
		if (document.getElementById(elem) !== null) {
			return document.getElementById(elem);
		}
		else if (typeof(document.getElementsByName(elem)) != 'undefined'){
			return document.getElementsByName(elem)['0'];
		}
		else {
			return null;
		}
	},
	
	//@new_node	xml node (noeud à insérer)
	//@node		xml node (noeud cible)
	//desc		cette fonction "simule" une fonction insertAfter
	insertAfter : function (new_node, node) {
		//on determine le noeud parent
		var parent_node = node.parentNode.parentNode;
		//on determine le noeud suivant
		var next_node = node.parentNode.nextSibling;
		//si il y a des lignes blanches entre les deux (3 = text)
		/*while(next_node.nodeType == 3 && next_node.nextSibling !== null) { 
			try
			{
				//next_node = next_node.nextSibling;
			}
			catch(e)
			{
				showError(e);
			}
			
		}*/

		try
		{
			//si le noeud suivant n'existe pas cela signifie que node est le dernier est donc il suffit de faire un appendChild de parent_node
			if (next_node === null) {
				parent_node.appendChild(new_node,parent_node);
			}
			else {
				parent_node.insertBefore(new_node,next_node);
			}
		}
		catch(e)
		{
			showError(e);
		}
	},
	
	//http://www.truerwords.net/articles/web-tech/custom_events.html
	addEventListener : function ( element, event_name, observer, capturing ) {
        if ( element.addEventListener ) {  // the DOM2, W3C way
            element.addEventListener( event_name, observer, capturing );
		}
        else if ( element.attachEvent ) {  // the IE way
            element.attachEvent( "on" + event_name, observer );
		}
    },
	
	removeEventListener : function ( element, event_name, observer, capturing ) {
        if ( element.removeEventListener ) {  // the DOM2, W3C way
            element.removeEventListener( event_name, observer, capturing );
		}
        else if ( element.detachEvent ) {  // the IE way
            element.detachEvent( "on" + event_name, observer );
		}
    },
	
	//http://fn-js.info/snippets/bind
	//desc	cette fonction permet de propager un objet dans un environnement où il ne devrait normalement plus exister !!!!
	bind : function (obj, fun, args) {
		return function() {
		if (obj === true) {
			obj = this;
		}
		var f = typeof fun === "string" ? obj[fun] : fun;

		return f.apply(obj, Array.prototype.slice.call(args || [])
			.concat(Array.prototype.slice.call(arguments)));
		};
	}
};
