// encoding="UTF-8"

/*
 *	Web Basic Services (WBS) is a framework that proposes simple solutions using popular free softwares for usual web features.
 *	Copyright (C) 2009 Vincent Adelé
 *	
 *	This file is part of WBS.
 *	
 *	WBS is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.
 *	
 *	WBS is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *	GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with WBS. If not, see <http://www.gnu.org/licenses/>.
 */
 
/**
 *	Gestionnaire AJAX thread safe.
 *
 *	Exemple d'utilisation :
 * 		wbs_xhr_send(
 *			'ping.html',
 *			'GET',
 *			'',
 *			function( xhr_object ) {
 *				alert(xhr_object.status);
 *			}
 *		);
 *
 *	@author	Vincent Adele	<vincent.adele@unskontrollables.org>
 *	@copyright	Copyright (C) 2009 Vincent Adele
 *	@license	http://www.gnu.org/licenses/gpl.html	GNU General Public License
 *	@package	wbs
 *	@subpackage	js
 *	@version	1.0.0
 */
 
// Gestion d'appels AJAX

// Pile d'appels AJAX
wbs_xhr_calls = new Array();

// Classe d'un appel AJAX
// Cette classe possède deux propriétés :
// object : qui correspond à une instance de XMLHttpRequest ou ActiveXObject suivant le cas.
// callback : qui est la fonction appelée lors de la réception de la réponse par l'objet object. Cette fonction recevra un paramètre en entré : object.
function wbs_xhr_call() {
	
	// Creation de l'objet XMLHttpRequest
	if( window.XMLHttpRequest ) {
		this.object = new XMLHttpRequest();
		
	// Creation de l'objet ActiveX
	} else if (window.ActiveXObject) {
		try {
			this.object = new ActiveXObject('Msxml2.XMLHTTP');
		} catch (e) {
			try {
				this.object = new ActiveXObject('Microsoft.XMLHTTP');
			} catch (e) {
				// Echec de la creation
				return false;
			}
		}
		
	// Echec de la creation
	} else {
		return false;
	}
	
	this.callback = null;
}

// Fonction exécutée à la réception d'une réponse AJAX.
wbs_xhr_receive = function() {
	
	var xhr_object = null;
	for( var xhr_call_index=0; xhr_call_index<wbs_xhr_calls.length; xhr_call_index++) {
		
		xhr_object = wbs_xhr_calls[xhr_call_index].object;
		if( xhr_object.readyState == 4 ) {
			
			if( typeof wbs_xhr_calls[xhr_call_index].callback == 'function' ) {
				wbs_xhr_calls[xhr_call_index].callback( xhr_object );
			}
			
			wbs_xhr_calls.splice(xhr_call_index,1);
			xhr_call_index--;
		}
	}
}

// Appelle en AJAX
// url : URL de la page à charger.
// xhr_method : méthode d'appel 'GET' ou 'POST'
// xhr_post_data : données passées en POST au format 'var1=value1&var2=value2&...'
// xhr_callback : fonction exécutée à la réception de la réponse. Cette fonction doit prendre un paramètre en entré : l'objet XHR.
wbs_xhr_send = function( url, xhr_method, xhr_post_data, xhr_callback ) {
	
	// Creation de l'objet AJAX
	var xhr_call = new wbs_xhr_call();
	// Si la création a échouée, ...
	if( !xhr_call ) {
		// ... on échoue l'envoi.
		return false;
	}
	
	// Fonction de callback
	xhr_call.object.onreadystatechange = wbs_xhr_receive;
	xhr_call.callback = xhr_callback;
	
	// Appel
	xhr_call.object.open( xhr_method, url, true ); 
	xhr_call.object.setRequestHeader("X-Referer", document.location.href);
	xhr_call.object.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	xhr_call.object.send( xhr_post_data );
	
	// Enregistrement dans le stack
	wbs_xhr_calls.push( xhr_call );
	
	return true;
}

// Convertit une ancre en un appel AJAX. La fonction xhr_callback est ensuite exécutée avec la réponse.
wbs_xhr_anchor_overcome = function( object, xhr_callback ) {
	
	wbs_xhr_send(
		object.href,
		'GET',
		null,
		xhr_callback
	);
	
	return false;
}
// Convertit une ancre en un appel AJAX dans l'élément ayant le même identifiant que l'attribut target.
wbs_xhr_anchor_overcome_in_target = function( object ) {
	
	var anchor_target = document.getElementById( object.target );
	if( !anchor_target ) {
		return true;
	}
	
	wbs_xhr_anchor_overcome(
		object,
		function( xhr_object ) {
			wbs_set_innerHTML( anchor_target, xhr_object.responseText );
		}
	);
	
	return false;
}

// Convertit un formulaire en un appel AJAX. La fonction xhr_callback est ensuite exécutée avec la réponse.
wbs_xhr_form_overcome = function( object, xhr_callback ) {
	
	wbsAddEventListener(
		object,
		'submit',
		function( event ) {
			// On soumet le formulaire et on récupère la valeur de retour.
			// event.returnValue permet de prévenir l'action par défault sous IE.
			event.returnValue = wbs_xhr_form_submit( object, event, xhr_callback );
			// Si l'on doit prévenir l'action par défaut et que l'event est compatible DOM, ...
			if( !event.returnValue && event.preventDefault ) {
				// ... on utilise la méthode appropriée.
				event.preventDefault();
			}
			// On renvoit la valeur de retour au cas où l'on serait dans un système antérieur à l'utilisation des events.
			return event.returnValue;
		}
	);
	
	// Recherche des elements de validation
	var form_element;
	for( var form_elements_index=0; form_elements_index<object.elements.length; form_elements_index++ ) {
		
		form_element = object.elements[form_elements_index];
		switch( form_element.nodeName.toLowerCase() ) {
			
			case 'button':
			case 'input':
				if( 'submit' == form_element.type ) {
					// Ajout d'un event pour connaître la source de la validation.
					wbsAddEventListener(
						form_element,
						'click',
						function( event ) {
							if( !event.explicitOriginalTarget ) {
								wbs_explicitOriginalTarget = form_element;
							} else {
								wbs_explicitOriginalTarget = null;
							}
						}
					);
				}
				break;
				
			default:;
		}
	}
}
// Construit la requête AJAX du formulaire.
// Attention, si le formulaire est destiné à uploader un fichier, l'appel AJAX est simulé à l'aide d'une iframe.
wbs_xhr_form_submit = function( object, event, xhr_callback ) {
	
	// Si l'encodage du formulaire correspond à un upload de fichier, ...
	if( 'multipart/form-data'==object.enctype ) {
		// ..., on recherche si il y a effectivement un fichier à transmettre.
		for( var form_elements_index=0; form_elements_index<object.elements.length; form_elements_index++ ) {
			
			var form_element = object.elements[form_elements_index];
			// Si l'élément n'est pas un input, ...
			if( 'input'!=form_element.nodeName.toLowerCase() ) {
				// ... on passe.
				continue;
			}
			// Si l'élément n'est pas de type file, ...
			if( 'file'!=form_element.type.toLowerCase() ) {
				// ... on passe.
				continue;
			}
			// Si il y a un élément input de type file, on utilise la simulation par iframe.
			return wbs_xhr_form_submit_through_iframe( object, event, xhr_callback );
		}
	}
	
	// Construction des données
	var form_data = new Array();
	for( var form_elements_index=0; form_elements_index<object.elements.length; form_elements_index++ ) {
			
		var form_element = object.elements[form_elements_index];
		
		// Si l'élément n'a pas de nom, ...
		if( !form_element.name ) {
			// ... aucune donnée n'est à envoyer.
			continue;
		}
		
		// Traitement du tag au cas par cas.
		form_element_nodeName_loop:
		switch( form_element.nodeName.toLowerCase() ) {
			
			case 'select':
				// Si aucun élément n'est sélectionné, ...
				if( -1==form_element.selectedIndex ) {
					// ... on n'a rien à faire.
					break;
				}
				// Si c'est un select multiple, ...
				if( form_element.multiple ) {
					// ... on parcours toutes les options, ...
					for( var option_index=0; option_index<form_element.length; option_index++ ) {
						var option = form_element.options[option_index];
						// ... si l'option est sélectionnée, ...
						if( option.selected ) {
							// ... on l'enregistre.
							form_data.push( form_element.name+'='+option.value );
						}
					}
				// Si c'est un select simple, ...
				} else {
					// ..., on enregistre l'option sélectionnée.
					form_data.push( form_element.name+'='+form_element.options[form_element.selectedIndex].value );
				}
				break;
				
			case 'button':
			case 'input':
				// Traitement du type au cas par cas.
				switch( form_element.type.toLowerCase() ) {
					
					case 'submit' :
						// Ajout du bouton submit éventuel
						if( 'undefined' != typeof event && event.explicitOriginalTarget ) {
							if( event.explicitOriginalTarget==form_element ) {
								break;
							}
						} else if( 'undefined' != typeof wbs_explicitOriginalTarget ) {
							if( wbs_explicitOriginalTarget==form_element ) {
								break;
							}
						}
					case 'button' :
					case 'reset' :
						break form_element_nodeName_loop;
						
					case 'checkbox' :
					case 'radio' :
						// Si l'élément n'est pas sélectionné, ...
						if( !form_element.checked ) {
							// ... on n'a rien à faire.
							break form_element_nodeName_loop;
						}
						break;
						
					default:;
				}
				// Ne pas mettre de break ici afin de laisser passer les inputs non traités.
				
			default:
				form_data.push( form_element.name+'='+form_element.value );
		}
	}
	
	if( 'get'==object.method.toLowerCase() ) {
		var xhr_url = object.action+'?'+form_data.join( '&' );
		var xhr_data = null;
	} else {
		var xhr_url = object.action;
		var xhr_data = form_data.join( '&' );
	}
	
	wbs_xhr_send(
		xhr_url,
		object.method,
		xhr_data,
		xhr_callback
	);
	
	return false;
}
// Simulation d'un appel AJAX d'un formulaire.
wbs_xhr_form_submit_through_iframe = function( object, event, xhr_callback ) {
	
	// Création de l'iframe
	var iframe = document.createElement( 'iframe' );
	
	// Attribution d'un nom unique
	var iframe_number = 1;
	while( 0<document.getElementsByName( 'wbs_xhr_form_recipient_'+iframe_number ).length ) {
		iframe_number++;
	}
	iframe.setAttribute( 'name', 'wbs_xhr_form_recipient_'+iframe_number );
	
	// Inclusion de l'iframe dans le formulaire
	var iframe_container = document.createElement( 'div' );
	iframe_container.setAttribute( 'style', 'display:none;' );
	iframe_container.appendChild( iframe );
	object.appendChild( iframe_container );
	
	// Gestion du retour du formulaire
	var form_target = object.target;
	wbsAddEventListener(
		iframe,
		'load',
		function( event ) {
			
			// Retablissement du formulaire
			if( form_target ) {
				form.setAttribute( 'target', form_target );
			} else {
				form.removeAttribute( 'target' );
			}
			
			// Simulation d'un object XHR
			var xhr_call = new wbs_xhr_call();
			xhr_call.object.readyState = 4;
			xhr_call.object.status = 200;
			xhr_call.object.responseText = iframe.contentWindow.document.getElementsByTagName( 'html' )[0].innerHTML;
			xhr_call.object.responseXml = xhr_call.object.responseText;
			xhr_call.object.onreadystatechange = xhr_callback;
			xhr_call.object.onreadystatechange( xhr_call.object );
			
			// Suppression de l'iframe obsélète
			var iframe_parent = iframe_container.parentNode;
			iframe_parent.removeChild( iframe_container );
		}
	);
	
	// Redirection du formulaire
	object.setAttribute( 'target', iframe.name );
	
	return true;
}

