Probabilmente una cosa che molti sviluppatori hanno sempre sognato di fare è l’upload dei files sul server trascinandoli direttamente dal proprio desktop, o dal file system più in generale, nel browser. Ecco con html5 questa cosa finalemnte si potrà fare, vediamo come.
Partiamo dall’html:
<section id="wrapper"> <header><h1>Drag and drop, upload automatico</h1></header> <article> <div id="holder"></div> <p id="upload"><label>Drag & drop non supportato, procedi attraverso il campo di input:<br><input type="file"></label></p> <p id="filereader">File API & FileReader API non supportato</p> <p id="formdata">XHR2 FormData non supportato</p> <p id="progress">XHR2 upload progress non supportato</p> <p>Upload progress: <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p> <p>Trascina un'immagine dal tuo desktop nella drop zone, vedrai nel browser il render della preview, ma verrà anche automaticamente fatto l'upload sul server.</p> </article> </section>
Oltre al titolo abbiamo il div id=”holder” che sarà l’aea di rilascio dell’oggetto; quattro paragarafi contenenti i messaggi di errore relativi a funzionalità html5 che potrebbero non essere supportate da tutti i browser, il tag progress per la gestione della progress bar relativa all’upload e un paragrafo di istruzioni.
Rapidamente anche il css, o almeno una parte:
#holder { border: 10px dashed #ccc; width: 300px; min-height: 300px; margin: 20px auto;} #holder.hover { border: 10px dashed #0c0; } #holder img { display: block; margin: 10px auto; } #holder p { margin: 10px; font-size: 14px; } progress { width: 100%; } progress:after { content: '%'; } .fail { background: #c00; padding: 2px; color: #fff; } .hidden { display: none !important;}
Abbastanza importante la formattazione di #holder che deve rendere facilmente riconoscibile la drop zone,
per il resto da notare la classe .hidden per nascondere le funzionalità supportate.
Ma veniamo al cuore javascript del nostro lavoro e iniziamo con la definizione delle variabili che ci serviranno:
var holder = document.getElementById('holder'), tests = { filereader: typeof FileReader != 'undefined', dnd: 'draggable' in document.createElement('span'), formdata: !!window.FormData, progress: "upload" in new XMLHttpRequest }, support = { filereader: document.getElementById('filereader'), formdata: document.getElementById('formdata'), progress: document.getElementById('progress') }, acceptedTypes = { 'image/png': true, 'image/jpeg': true, 'image/gif': true }, progress = document.getElementById('uploadprogress'), fileupload = document.getElementById('upload');
Oltre ai document.getElementById ci interessa la definizione dei test che dovremo attuare prima di poter procedere cioè relativi al supporto delle API FileReader, Drag&Drop, FormData e del tag progress e dei tipi di dato, immagini, accettate dal nostro uploader. Aquesto punto possiamo eseguire il test:
"filereader formdata progress".split(' ').forEach(function (api) { if (tests[api] === false) {support[api].className = 'fail';} else {support[api].className = 'hidden'; } });
Mettiamo i test da compiere in una stringa, la splittiamo creando un’array sul quale fare un ciclo foreach, se il test fallisce applichiamo la classe css fail per evidenziare il paragrafo, viceversa lo nascondiamo. Aquesto punto se il nostro browser supporta le funzionalità necessarie possiamo procedere. O meglio attuiamo l’ultimo test sul drag&drop e gestiamo il tutto:
if (tests.dnd) { holder.ondragover = function () { this.className = 'hover'; return false; }; holder.ondragend = function () { this.className = ''; return false; }; holder.ondrop = function (e) { this.className = ''; e.preventDefault(); readfiles(e.dataTransfer.files); } } else { fileupload.className = 'hidden'; fileupload.querySelector('input').onchange = function () { readfiles(this.files); }; }
Ovvero se il test fallisce passiamo alla modalità input tradizionale, altrimenti associamo agli eventi dragover e dragend le opprtune classi css prima definite, ma soprattutto all’ondrop chiamiamo la funzione readfiles() assando come argomento il file caricato. La funzione readiles dovrà fare 3 cose: mostrare il file nella preview e di questo si occuperà un’apposita funzione, passare il file al server e infine gestire la progress bar.
Procediamo in ordine e iniziamo dalla preview:
var formData = tests.formdata ? new FormData() : null; for (var i = 0; i < files.length; i++) { if (tests.formdata) formData.append('file', files[i]); previewfile(files[i]); } function previewfile(file) { if (tests.filereader === true && acceptedTypes[file.type] === true) { var reader = new FileReader(); reader.onload = function (event) { var image = new Image(); image.src = event.target.result; image.width = 250; // a fake resize holder.appendChild(image); }; reader.readAsDataURL(file); } else { holder.innerHTML += '<p>Uploaded ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : ''); console.log(file); } }
Nel caso specifico quindi la funzione si occupa di testare il supporto dell’API, che l’immagine sia fra quelle accettate e infine di “appenderla” nella pagina a una larghezza fissa di 250px
Il secondo step è costituito dall’upload del file tramite ajax
if (tests.formdata) { var xhr = new XMLHttpRequest(); xhr.open('POST', 'upload.php'); xhr.onload = function() { progress.value = progress.innerHTML = 100; };
Un po’ vecchia maniera!. L’upload php si occuperà della gestione server side del file caricato, per esempio così:
$upload_dir=$_SERVER['DOCUMENT_ROOT'].'/upload/'; $filename=$_FILES['file']['name']; if(is_uploaded_file($_FILES['file']['tmp_name'])){ move_uploaded_file($_FILES['file']['tmp_name'],$upload_dir.$filename) or die($upload_dir.$filename); }
Il passaggio precedente si completa e integra con l’invio della chiamata e la sincronizzazione della progress bar:
if (tests.progress) { xhr.upload.onprogress = function (event) { if (event.lengthComputable) { var complete = (event.loaded / event.total * 100 | 0); progress.value = progress.innerHTML = complete; } } } xhr.send(formData); }
L’esempio