Mar 212013
 

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 &amp; drop non supportato, procedi attraverso il campo di input:<br><input type="file"></label></p>
<p id="filereader">File API &amp; 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

Mar 192013
 

Una delle novità più interessanti di HTML5 che si sta diffondendo è il drag&drop, funzionalità non nuova al 100% perché già resa disponibile da librerie javascript come per esempio jQuery UI, ma che presenta interessanti novità oltre, ovviamente, ad essere disponibile nativamente nei browser. Già i browser, trattandosi di html5 si pone il solito problema dello stato del supporto, su caniuse possiamo farci un’idea rispetto alla nostra funzione.

Vediamo come funziona il nostro drag&drop.

Come prima cosa inseriamo nel nostro documento html un div che servirà da drop area e poi un’immagine da trascinare, questo elemento avrà l’attributo draggable=”true”:


<div id="box1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<img id="drag1" src="images/articolo.gif" draggable="true" ondragstart="drag(event)">

Oltre a quanto descritto è già stato inserito il codice javascript per la gestione degli eventi, il primo, ongragstart, si registra all’inizio del trascinamento; sul box invece abbiamo intercettato due eventi: ondragover, ovvero la sovrapposizione tra l’elemento trascinato e l’area di atterraggio e ondrop, ovvero il rilascio vero e proprio.

Vediamo cosa accade evento per evento partendo dal dragstart:


function drag(ev){
ev.dataTransfer.setData("Text",ev.target.id);
}

Il metodo dataTransfer.setData () imposta il tipo di dati e il valore dei dati trascinati: nel nostro caso dentro Text l’id dell’immagine che trasciniamo.

L’evento onDragOver specifica dove i dati trascinati possono essere rilasciati.


function allowDrop(ev){
ev.preventDefault();
}

Per impostazione predefinita, i dati / elementi non possono essere rilasciati in altri elementi. Per consentire il drop quindi dobbiamo evitare la gestione predefinita.

Infine il drop vero e proprio:


function drop(ev){
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}

Dove si va a leggere l’id dell’elemento in fase di trascinamento e con il metodo appendChild applicato al target, il nostro div, aggiungiamo la nostra immagine.

L’esempio