codice:
// (C) Andrea Giammarchi
// closure per non avere problemi di ambiguità con la funzione dollaro
// inviata come variabile globale jQuery
(function($){$(function(){
// funzione fulcro della gestione upload
// il this è riferito alla form del caso
function APCQuery(){
var i = 0, // indice
ids = []; // lista di id univoci creati per inviare al server
// dei valori "affidabili"
// per ogni input di tipo file ...
$(this).find("input").filter("[type=file]").each(function(){
// si aggiunge un campo hidden immediatamente prima
// utilizzando il nome APC_UPLOAD_PROGRESS ed un valore presumibilmente e preferibilmente univoco
$(this).before(
'<input type="hidden" name="APC_UPLOAD_PROGRESS[]" value="' +
// valore salvato nella lista di id
(ids[i++] = i + Math.random()) +
'" />'
);
});
// l'iframe utilizzato come target non è ancora presente
// è fondamentale inserirlo in un nodo DOM esterno al form
// altrimenti non verrebbe accettato come target
$(this.parentNode).append(
'<iframe class="apcquery" name="' +
// il nome dell'iframe deve essere univoco e per far si che questo avvenga
// lo si assegna sfruttando un prefisso fittizio "apcqueryiframe"
// più l'insieme degli id univoci concatenati tramite un carattere qualunque,
// possibilmente ASCII standard, in questo caso il carattere "-"
(this.target = "apcqueryiframe" + ids.join("-")) +
'" id="'
// per compatibilità si assegna anche l'id utilizzando
// lo stesso nome univoco scelto per il name
+ this.target +
'" src="' +
// per evitare problemi (avvisi) in ambiente sicuro (SSL)
// è sempre meglio mettere un source all'iframe
// (Internet Explorer chiederebbe una conferma codice insicuro se omesso)
(this.action = this.action.split("?")[0]) +
'"></iframe>'
);
// da notare che durante la creazione dell'iframe sono stati riassegnati
// sia il target che l'action della form.
// Nel primo caso, il target, si aggiunge l'iframe per evitare che il submit
// della form vada all'altra pagina mentre nel secondo, l'action,
// si elimina la querystring dedicata al redirect e quindi
// agli utenti che non hanno JavaScript abilitato o che non sono
// compatibili con questo codice
// non resta che assegnare l'evento "onsubmit" della form ...
$(this).submit(function(){
// per prima cosa l'evento deve capire se effettivamente c'è qualcosa da fare ...
// dato che si sfrutta il client per l'upload, tanto vale sfruttarlo anche per diminuire le chiamate al server
var result = !!$(this).find("input").filter("[type=file]").filter(function(){return !!this.value}).length;
// se tra i vari input di tipo file almeno uno è stato specificato ed ha quindi un valore
// si procede con il submit
if(result){
var delay = 500, // tempo, in millisecondi, ogni quanto il client chiede informazioni al server
timeout = 0, // intervallo assegnato dalla funzione setTimeout
apcquery = $(this).find("div.apcquery"), // contenitore informazioni all'interno della form
iframe = $(this.parentNode).find("iframe[name=" + this.target + "]").get(0), // l'iframe utilizzato come target del form
self = this, // riferimento alla form stessa, utile per le funzioni innestate in intervalli e/o altro
onload = function(){ // callback richiamata ad iframe caricato
// si annullano gli eventi precedentemente assegnati all'iframe
iframe.onload = iframe.onreadystatechange = function(){};
// si annulla il timeout precedentemente assegnato
clearTimeout(timeout);
// si elabora il risultato presente, grazie alla classe PHP5, nella window dell'iframe e non nel documento
// (più compatibilità per meno operazioni)
APCResult(apcquery, iframe.contentWindow.response, function(){
// si mostra per il tempo scelto le informazioni complete ...
setTimeout(function(){
// si riabilita il bottone submit
$(self).find("input[type=submit]").each(function(){
this.disabled = false;
});
// e si rinascondono le informazioni
apcquery.css("visibility", "hidden");
}, delay);
});
};
// proprietà utilizzata esclusivamente da Explorer ma se presente, anche da Opera
iframe.onreadystatechange = function(){
// se lo stato dell'iframe è caricato o completo
if(/loaded|complete/i.test(iframe.readyState))
// si richiama la callback onload
onload();
};
// evento utilizzato da tutti i browsers tranne che da Explorer (al solito insomma)
iframe.onload = onload;
// da notare che non è possibile assegnare la callback onload direttamente ad iframe.onload
// per poi richiamarla all'onreadystatechange per il semplice motivo che Explorer non darà
// errore ... ma non effettuerà nemmeno la chiamata (al solito insomma ...)
// tentativo di evitare doppio upload, stress inutile per il server
$(this).find("input[type=submit]").each(function(){
// si disabilita il submit della form (uno o più di uno se presenti)
this.disabled = true;
});
// si assegna il valore in percentuale allo span dedicto a mostrare lo stato sotto forma di barra (zero inizialmente)
apcquery.find(".percent").css("width", "0%");
// si mostra il contenitore di informazioni e si assegna il valore zero a tutti i campi
apcquery.css("visibility", "visible").find(".total,.loaded,.rate").text(APCSize(0));
// si assegna un timeout per cominciare a richiedere informazioni al server
// ... è totalmente inutile farlo immediatamente, l'upload deve ancora cominciare ...
timeout = setTimeout(function(){
var i = 0, // indice array
result = [], // array risultati
callee = arguments.callee; // questa stesa funzione
// per ogni input che invia l'informazione APC_UPLOAD_PROGRESS ...
$(self).find("input")
.filter(function(){
return this.name === "APC_UPLOAD_PROGRESS[]"
})
// si assegna ala lista risultati la chiave APC_PK contenente il valore dell'input
// gestiti entrambi dalla classe PHP5
.each(function(){
result[i++] = 'APC_PK[]=' + this.value;
});
// si richiama la pagina server, in questo caso la classe PHP5, concatenando
// le chiavi ed i rispettivi valori
$.getJSON(self.action + "?" + result.join("&"),
// una volta ricevuto il risultato ...
function(data){
// lo si invia alla funzione dedicata al fine di mostrare quanto
// ricevuto dal server
APCResult(apcquery, data, function(){
// questa callback serve per evitare di intasare il server di chiamate
// Se si utilizza un intervallo infatti il client
// continuerà impoerterrito a richiedere informazioni mentre
// l'opzione più ovvia è attendere che almeno l'ultima richiesta
// sia stata elaborata.
// tutto questo è gestito nella funzione APCResult
timeout = setTimeout(callee, delay);
})
});
}, delay);
};
// si restituisce il risultato per attivare o disattivare il submit
// (se non c0è alcun input di tipo file selezionato o con un valore restituisce false, non accade niente)
return result;
});
};
// funzione di gestione risultati
function APCResult(div, data, callback){
var loaded = 0, // totale bytes inviati
total = 0, // totale bytes necessari per terminare l'upload
i = 0; // indice array (data)
// è necessario avere un riferimento per gestire
// il rate, in questa pillola presente fin da subito
// e non solo ad upload terminato, come previsto da APC
if(!div.__APCRate)
div.__APCRate = {
time:new Date, // tempo richiesta
value:0 // dati scaricati
};
while(i < data.length){
// se data[i] non contiene il valore false,
// default quando il server non sa cosa fare,
// si assegnano tutti i caricati e tutti i totali
if(data[i]){
loaded += data[i].current;
total += data[i].total;
};
++i;
};
// per avere un rate verosimile si sottrae il totale dati caricati
// a quelli precedenti per poter gestire un rapporto upload / tempo trascorso
div.__APCRate.value = loaded - div.__APCRate.value;
// si scrive il totale tramite la funzione APCSize
div.find(".total").text(APCSize(total));
// si scrive il totale tramite la funzione APCSize
div.find(".loaded").text(APCSize(loaded));
// si scrive il rate eliminando la dicitura bytess (convertita in bps)
div.find(".rate").text(APCSize(parseInt((div.__APCRate.value * 100) / (new Date - div.__APCRate.time)) || 0).replace(/bytes/, "b") + "ps");
// si assegna la percentuale in base al rapporto bytes uploadati / totale bytes da uploadare
div.find(".percent").css("width", (Math.round((loaded * 100) / total) || 0)+ "%");
// si aggiorna il tempo per il rate successivo
div.__APCRate.time = new Date;
// se è stata specificata una callback la si richiama
// ovvero si assegna il prossimo setTimeout per avere informazioni
// o si esce dal "programma" - caso onload
if(callback)
callback();
};
// resittuisce un risultato compresinbile, da bytes a Kb, Mb o altro
function APCSize(value){
var type = ["bytes", "Kb", "Mb", "Gb", "Tb", "Zb"],
i = 0;
while(value > 1024 && ++i)
value /= 1024;
return (i ? value.toFixed(2) : Math.ceil(value)) + " " + type[i];
};
// si avvia il programma automatizzato scegliendo solo
// le form della pagina che contengono un input di tipo file
$("form").each(function(){
if(0 < $(this).find("input").filter("[type=file]").length)
APCQuery.call(this);
});
// normalizzazione numerica, funzione toFixed non presente in IE5.5 o inferiore
if(!Number.prototype.toFixed)
Number.prototype.toFixed = function(max){
var result = Math.pow(10, parseInt(max)), tmp;
result = String(Math.round(this * result) / result);
if(max > 0){
tmp = result.split(".");
tmp[1] = (tmp[1] || "") + new Array(++max - (tmp[1] ? tmp[1].length : 0)).join("0");
result = tmp.join(".");
};
return result;
};
})})(jQuery);