Gérer le drag&drop et le téléchargement de fichiers en Javascript

Depuis quelques temps, il existe au sein de nos chers navigateurs, l’API FileReader, qui permet, comme le résume bien cet article de MDN :

  • de gérer les fichiers sélectionnés via les champs de formulaire « fichier »
  • de supporter le glisser/déposer de fichiers dans le navigateur

Les extraits de code qui suivent sont fortement inspirés de l’article de Maxime Chaillou sur le drag&drop.

On crée tout d’abord une zone qui servira de réceptable à nos fichiers :

<div id="dropfile">Drop your file here to deduplicate SMS</div>

On gère ensuite les évènements sur cet élement :

//Quand la souris, maintenue enfoncée, entre dans la zone, on la colore, pour indiquer l'interaction
$(document).on('dragenter', '#dropfile', function() {
		$(this).css('border', '3px dashed red');
		return false;
});
//Une fois que la souris est rentrée dans la zone, on actualise régulièrement l'état de la zone
$(document).on('dragover', '#dropfile', function(e){
			e.preventDefault();
			e.stopPropagation();
			$(this).css('border', '3px dashed red');
			return false;
});
//Si la souris, toujours enfoncée, ressort, on indique la fin de l'interaction
$(document).on('dragleave', '#dropfile', function(e) {
			e.preventDefault();
			e.stopPropagation();
			$(this).css('border', '3px dashed #BBBBBB');
			return false;
});

Il faut ensuite gérer l’upload à proprement parler :

$(document).on('drop', '#dropfile', function(e) {
	//Si le transfert est possible
	if(e.originalEvent.dataTransfer){
		//S'il y au moins un fichier
		if(e.originalEvent.dataTransfer.files.length) {
			//On bloque l'ouverture du fichier dans le navigateur, ce qui est l'action par défaut
			e.preventDefault();
			//On bloque ensuite la propagation de l'évènement "dépot de fichiers" pour éviter de déclencher toute action sur les éléments parents
			e.stopPropagation();
			$(this).css('border', '3px dashed green');
			//On lance l'upload à proprement parler
			upload(e.originalEvent.dataTransfer.files);
		}  
	}
	else {
			   $(this).css('border', '3px dashed #BBBBBB');
	}
	return false;
});

La function upload() alors utilisée va nous permettre d’envoyer le contenu du fichier déposé à une autre fonction (par exemple pour l’envoyer à un serveur Web) :

function upload(files) {
	var f = files[0] ;
	var reader = new FileReader();

	//La fonction handleReaderLoad est ici associée à l'évènement "onload", pour qu'elle se déclenche une fois le chargement du fichier terminé
	reader.onload = handleReaderLoad;

	//Pour les besoins de cette démonstration, on utilise ici readAsText() qui va lire le fichier sous forme de chaîne de caractères
	//Mais on utilise plutôt readAsBinary, pour l'envoi à un serveur Web
	reader.readAsText(f);            
}

La dernière fonction va quant à elle gérer le fichier à proprement parler (l’envoyer via Ajax, ou, comme ici, traiter la chaîne de caractères comme un XML et en dédoublonner le contenu) :

function handleReaderLoad(evt) {
	var xml = evt.target.result
	deduplicateSMS($.parseXML(xml));
}

Pour cet exemple (qui vise à supprimer les SMS en double dans une sauvegarde XML), on utilise également le plugin jQuery FileSaver.js, qui permet de lancer le téléchargement de fichiers dans un navigateur depuis Javascript :

function deduplicateSMS(xmlSource) {
	var ids = new Array();
	var duplicates= 0;
	var total= 0;
	
	//On explore le XML et on vérifie si la clé de chaque noeud existe déja dans le tableau "ids"
	$(xmlSource).find('allsms sms').each(function(index,e) {
		if (ids.indexOf($(this).attr('address')+$(this).attr('date'))==-1) {
			total+=1;
			ids.push($(this).attr('address')+$(this).attr('date'));
		} else {
			//Si c'est le cas, on le supprime
			duplicates+=1;
			$(this).remove();
		}
	});
	$(xmlSource).find('allsms').attr('count',total);
	//On convertit ensuite ce XML en chaîne de caractères via XMLToString, puis en "Blob" gérable par FileSaver.js
	var blob = new Blob([XMLToString(xmlSource)], {type: "text/xml;charset=utf-8"});
	//On lance ensuite le téléchargement
	saveAs(blob, "sms.xml");
}

La méthode XMLToString utilisée ici est très simple et permet juste de convertir un arbre XML en chaîne :

/* Source : http://www.dotnet-tricks.com/Tutorial/javascript/Y7Q9130612-Convert-string-to-xml-and-xml-to-string-using-javascript.html*/
function XMLToString(oXML)
{
	 //code for IE
	 if (window.ActiveXObject) {
	 var oString = oXML.xml; return oString;
	 } 
	 // code for Chrome, Safari, Firefox, Opera, etc.
	 else {
		
	 return (new XMLSerializer()).serializeToString(oXML);
	 }
 }

Vous pouvez retrouver cet exemple sur Github : https://github.com/samy-r/sms-super-backup-deduplication

Une démo est disponible sur mon espace de test : http://lahaut.info/demos/sms-super-backup-deduplication/