Utilizzo del caricamento di file HTML5 con AJAX e jQuery


84

Certo, ci sono domande simili in giro su Stack Overflow, ma sembra che nessuna soddisfi le mie esigenze.

Ecco cosa sto cercando di fare:

  • Carica un'intera forma di dati, di cui una parte è un singolo file
  • Lavora con la libreria di caricamento file di Codeigniter

Fino a qui va tutto bene. I dati vengono inseriti nel mio database quando ne ho bisogno. Ma vorrei anche inviare il mio modulo tramite un post AJAX:

  • Utilizzando l'API file HTML5 nativa, non flash o una soluzione iframe
  • Preferibilmente interfacciarsi con il .ajax()metodo jQuery di basso livello

Penso di poter immaginare come farlo caricando automaticamente il file quando il valore del campo cambia utilizzando javascript puro, ma preferirei fare tutto in un colpo solo per l'invio in jQuery. Penso che non sia possibile farlo tramite le stringhe di query poiché ho bisogno di passare l'intero oggetto file, ma a questo punto sono un po 'perso su cosa fare.

Può essere raggiunto?


Non ho idea della parte Codeigniter, ma per la parte jQuery, dai un'occhiata a questo plugin .
BalusC

Risposte:


93

Non è troppo difficile. In primo luogo, dai un'occhiata all'interfaccia FileReader .

Quindi, quando il modulo viene inviato, prendi il processo di invio e

var file = document.getElementById('fileBox').files[0]; //Files[0] = 1st file
var reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = shipOff;
//reader.onloadstart = ...
//reader.onprogress = ... <-- Allows you to update a progress bar.
//reader.onabort = ...
//reader.onerror = ...
//reader.onloadend = ...


function shipOff(event) {
    var result = event.target.result;
    var fileName = document.getElementById('fileBox').files[0].name; //Should be 'picture.jpg'
    $.post('/myscript.php', { data: result, name: fileName }, continueSubmission);
}

Quindi, sul lato server (cioè myscript.php):

$data = $_POST['data'];
$fileName = $_POST['name'];
$serverFile = time().$fileName;
$fp = fopen('/uploads/'.$serverFile,'w'); //Prepends timestamp to prevent overwriting
fwrite($fp, $data);
fclose($fp);
$returnData = array( "serverFile" => $serverFile );
echo json_encode($returnData);

O qualcosa del genere. Potrei sbagliarmi (e se io sono, per favore, correggetemi), ma questo dovrebbe memorizzare il file come qualcosa di simile 1287916771myPicture.jpga /uploads/sul server e rispondere con una variabile JSON (a una continueSubmission()funzione) che contiene il nome del file sul server.

Controlla fwrite()e jQuery.post().

Nella pagina sopra viene descritto in dettaglio come utilizzare readAsBinaryString(), readAsDataUrl()e readAsArrayBuffer()per altre esigenze (ad esempio immagini, video, ecc.).


Hey Clark, ho capito bene? Questo invierà il file caricato non appena viene caricato nel costruttore FileReader dal file system, bypassando il gestore .ajax di basso livello di jQuery. Quindi il resto del modulo verrà inviato normalmente?
Joshua Cody,

Va bene, quindi prima mi sbagliavo nella mia comprensione. Ora sto prendendo il readAsDataUrl di un'immagine, aggiungendolo al mio datastring in .ajax e inviando tutte le mie informazioni insieme. La mia soluzione precedente prevedeva la classe di input file predefinita di CodeIgniter che acquisiva dati da $ _FILES ['field'], quindi sembra che dovrò passare a una soluzione diversa per analizzare i dati dell'immagine base64. Qualsiasi consiglio in merito è ben accetto, votando qui la tua risposta e, una volta completata l'implementazione, la contrassegnerò come corretta.
Joshua Cody

1
@ Joshua Cody - Ho aggiornato la risposta per fornire un po 'più di dettagli. Dovrai perdonare che non ho usato CodeIgniter in molte lune e non ho potuto dirti come integrarlo nella loro base di codice. Non sono sicuro del motivo per cui devi caricare il file prima dell'invio, ma questo dovrebbe almeno darti un indizio. (Puoi anche inserire l'immagine in un database, se è meglio per te).
clarkf

@Clarkf, non ho bisogno di caricare prima dell'invio, stavo fraintendendo il tuo esempio precedente :) Dopo che SO è andato giù e ho passato un po 'di tempo su w3 e HTML5Rocks , ho iniziato a capire. Ci proverò e torno qui.
Joshua Cody

Va bene, ci sto scherzando tutta la mattina. PHP sembra restituire file formattati male. Visualizza due immagini , una renderizzata immediatamente e l'altra dopo un $ _POST al server e un'eco immediata. Un diff sui due elementi rivela questo , che apparentemente PHP sta rimuovendo tutti i caratteri "+". Un str_replace risolve questo problema per la restituzione immediata, ma il file salvato è ancora danneggiato e non può essere aperto tramite il mio file system. Inoltre, andando avanti e contrassegnandolo come corretto. Grazie mille per il tuo aiuto finora.
Joshua Cody

6

Con jQuery (e senza FormData API) puoi usare qualcosa del genere:

function readFile(file){
   var loader = new FileReader();
   var def = $.Deferred(), promise = def.promise();

   //--- provide classic deferred interface
   loader.onload = function (e) { def.resolve(e.target.result); };
   loader.onprogress = loader.onloadstart = function (e) { def.notify(e); };
   loader.onerror = loader.onabort = function (e) { def.reject(e); };
   promise.abort = function () { return loader.abort.apply(loader, arguments); };

   loader.readAsBinaryString(file);

   return promise;
}

function upload(url, data){
    var def = $.Deferred(), promise = def.promise();
    var mul = buildMultipart(data);
    var req = $.ajax({
        url: url,
        data: mul.data,
        processData: false,
        type: "post",
        async: true,
        contentType: "multipart/form-data; boundary="+mul.bound,
        xhr: function() {
            var xhr = jQuery.ajaxSettings.xhr();
            if (xhr.upload) {

                xhr.upload.addEventListener('progress', function(event) {
                    var percent = 0;
                    var position = event.loaded || event.position; /*event.position is deprecated*/
                    var total = event.total;
                    if (event.lengthComputable) {
                        percent = Math.ceil(position / total * 100);
                        def.notify(percent);
                    }                    
                }, false);
            }
            return xhr;
        }
    });
    req.done(function(){ def.resolve.apply(def, arguments); })
       .fail(function(){ def.reject.apply(def, arguments); });

    promise.abort = function(){ return req.abort.apply(req, arguments); }

    return promise;
}

var buildMultipart = function(data){
    var key, crunks = [], bound = false;
    while (!bound) {
        bound = $.md5 ? $.md5(new Date().valueOf()) : (new Date().valueOf());
        for (key in data) if (~data[key].indexOf(bound)) { bound = false; continue; }
    }

    for (var key = 0, l = data.length; key < l; key++){
        if (typeof(data[key].value) !== "string") {
            crunks.push("--"+bound+"\r\n"+
                "Content-Disposition: form-data; name=\""+data[key].name+"\"; filename=\""+data[key].value[1]+"\"\r\n"+
                "Content-Type: application/octet-stream\r\n"+
                "Content-Transfer-Encoding: binary\r\n\r\n"+
                data[key].value[0]);
        }else{
            crunks.push("--"+bound+"\r\n"+
                "Content-Disposition: form-data; name=\""+data[key].name+"\"\r\n\r\n"+
                data[key].value);
        }
    }

    return {
        bound: bound,
        data: crunks.join("\r\n")+"\r\n--"+bound+"--"
    };
};

//----------
//---------- On submit form:
var form = $("form");
var $file = form.find("#file");
readFile($file[0].files[0]).done(function(fileData){
   var formData = form.find(":input:not('#file')").serializeArray();
   formData.file = [fileData, $file[0].files[0].name];
   upload(form.attr("action"), formData).done(function(){ alert("successfully uploaded!"); });
});

Con l'API FormData devi solo aggiungere tutti i campi del tuo modulo all'oggetto FormData e inviarlo tramite $ .ajax ({url: url, data: formData, processData: false, contentType: false, type: "POST"})


1
Questa soluzione non affronta la limitazione che XMLHttpRequest.send () impone ai dati incanalati attraverso di essa. Quando viene passata una stringa (come il tuo multipart), send () non supporta i dati binari. La tua multiparte qui sarà trattata come una stringa utf-8 e soffocerà o corromperà i dati binari che non sono validi utf-8. Se hai davvero bisogno di evitare FormData, devi usare XMLHttpRequest.sendAsBinary () ( polyfill disponibile . Sfortunatamente questo significa che usare jQuery per la chiamata ajax diventa molto più difficile.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.