Upload Ajax avec Drag & Drop
Nous allons voir comment mettre en place un upload interactif via un drag & drop qui affiche la progression de l’envoi au fur et à mesure que l’envoi avance. Nous utiliserons jQuery afin de simplifier les requêtes.
Nous développerons ici 3 grandes fonctionnalités que vous pourrez ensuite remanier comme bon vous semble:
- Le drag & drop dans une zone
- L’upload de fichier via Ajax
- Le suivi de la progression des uploads en temps réel
Pour le drag & drop, nous avons ce qu’on appelle une drop zone, une zone où seront lâchés les fichiers. Cette zone est généralement marquée par un contours en pointillés et qui s’illumine quand vous passez dessus avec un fichier. En réalité, vous pouvez mettre en place une telle zone mais permettre de lâcher votre fichier sur toute la page et dès lors qu’un fichier passe au dessus, votre drop zone s’illumine pour autoriser le lâcher.
Avant tout il faut vérifier que les fonctionnalités que l’ont souhaite sont disponibles sur le client
1 2 3 4 |
function isAdvancedUploader() { var div = document.createElement('div'); return (('draggable' in div) || ('ondragstart' in div && 'ondrop' in div)) && 'FormData' in window && 'FileReader' in window; } |
Ensuite si ces fonctionnalités sont disponible, on prend en charge la drop zone et on surveille les événements liés au drop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
if( isAdvancedUploader() ) { var $dropZone = $('.uploader'); var $dropZone.addClass('uploader-handled'); // Bind events $dropZone .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) { // Prevent default events e.preventDefault(); e.stopPropagation(); }) .on('dragover dragenter', function () { // Highlight drop zone $dropZone.addClass('dragging-over'); }) .on('dragleave dragend drop', function (event) { // Remove highlight when leaving zone // dragleave is called hovering drop zone's children var isChild = !!$dropZone.find(event.relatedTarget).length; if( !isChild ) { $dropZone.removeClass('dragging-over'); } }) .on('drop', function (e) { startUpload(e.originalEvent.dataTransfer.files); }); } |
L’élément .uploader est ici la drop zone réellement considérée pour le drop. La classe uploader-handled lui est ajoutée quand il est pris en charge comme drop zone, ce qui permet de masquer un éventuel bouton d’upload classique à ce moment. Il possède également la classe dragging-over quand il se fait survoler par un fichier valide.
J’ai ajouté un fix au dragleave qui est appelé à chaque fois que le fichier passe au dessus d’un élément enfant à la drop zone, ce qui le fait clignoter.
On envoie ensuite les fichiers et on suit la progression.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
function startUpload(files) { // files is an FileList object & not an array var form = new FormData(); for( var i = 0; i < files.length; i++ ) { var file = fileList.item(i); setFileState(file, 'STARTING'); form.append('file[]', file); } var startDate = new Date(); $.ajax({ type: 'POST', url: '/files.json', data: form, dataType: 'json', processData: false, contentType: false, xhr: function () { var xhr = new window.XMLHttpRequest(); //Upload progress xhr.upload.addEventListener("progress", function (event) { var progress = Math.round(event.loaded * 100 / event.total) + '%'; var delay = (new Date()).getTime() - startDate.getTime(); if( delay > 3 ) { // Delay the speed display after 3 seconds progress += ' / ' + getHumanBytes(Math.round(event.loaded * 1000 / delay)) + '/s'; } console.log('Upload', delay, progress, event); setFileListState(files, 'UPLOADING, progress'); }, false); }).done(function (data) { console.log('Upload done, got', data); // Return an array of object containing for each file, the name, the status, the file information in case of success & the message in case of error data.forEach(respFile => { const file = files.find(file => file.name === respFile.name); if( respFile.status === 'ok' ) { setFileState('FINISHED', respFile.file); } else { // Upload ok but server rejected the file setFileState('ERROR', {status: 'InputError', message: respFile.message}); } }); }).fail(function (response, textStatus, errorThrown) { // Occur in case of network error, server error or parsing error console.log('Upload failed, got', response, textStatus, errorThrown); setFileListState(files, 'ERROR', {status: textStatus, message: errorThrown}); }); } function getHumanBytes(size) { const units = ['B', 'KB', 'MB', 'GB']; var unit = 0; while( size > 1000 && units[unit + 1] ) { unit++; size /= 1000; } return number_format(size, 1) + ' ' + units[unit]; } |
On communique ici avec une api REST qui répond en JSON. Pendant l’envoi, on calcule la progression en % et on affiche la vitesse après 3 secondes écoulées.
Pour l’exemple, j’ai ajouté quelques console.log mais je vous laisse les supprimer.
Implémentez les fonctions setFileState et setFileListState selon vos besoins, on ne voit ici que comment obtenir les informations, utilisez ensuite jQuery pour remplir vos éléments HTML avec.
Vous avez maintenant une application envoyant automatiquement les fichiers lâchés sur votre page et affichant la progression de l’upload.