Verwenden von HTML5-Datei-Uploads mit AJAX und jQuery

83

Zwar gibt es ähnliche Fragen zu Stack Overflow, aber es scheint, dass keine meinen Anforderungen entspricht.

Folgendes möchte ich tun:

  • Laden Sie eine ganze Form von Daten hoch, von denen ein Teil eine einzelne Datei ist
  • Arbeiten Sie mit der Datei-Upload-Bibliothek von Codeigniter

Bis hierher ist alles in Ordnung. Die Daten werden nach Bedarf in meine Datenbank aufgenommen. Ich möchte mein Formular aber auch über einen AJAX-Beitrag einreichen:

  • Verwenden Sie die native HTML5-Datei-API, kein Flash oder eine Iframe-Lösung
  • Vorzugsweise Schnittstelle mit der Low-Level- .ajax()jQuery-Methode

Ich denke, ich könnte mir vorstellen, wie das geht, indem ich die Datei automatisch hochlade, wenn sich der Wert des Feldes mit reinem Javascript ändert, aber ich würde es lieber auf einen Schlag erledigen, um es in jQuery einzureichen. Ich denke, es ist nicht möglich, über Abfragezeichenfolgen zu arbeiten, da ich das gesamte Dateiobjekt übergeben muss, aber ich bin ein wenig verloren, was zu diesem Zeitpunkt zu tun ist.

Kann das erreicht werden?

Joshua Cody
quelle
Ich habe keine Ahnung vom Codeigniter-Teil, aber für den jQuery-Teil suchen Sie nach diesem Plugin .
BalusC
3
verwandt: stackoverflow.com/questions/166221/…
Timo Huovinen

Antworten:

93

Es ist nicht zu schwer. Schauen Sie sich zunächst die FileReader-Schnittstelle an .

Wenn das Formular gesendet wird, fangen Sie den Übermittlungsprozess ab und

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);
}

Dann auf der Serverseite (dh 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);

Oder so ähnlich. Ich kann falsch sein (und wenn ich bin, bitte korrigieren Sie mich), aber dies sollte die Datei als etwas speichern , wie 1287916771myPicture.jpgin /uploads/auf dem Server, und reagiere mit einer JSON Variable (in continueSubmission()Funktion) den Dateinamen auf dem Server enthält.

Check out fwrite()und jQuery.post().

Auf der oben genannten Seite Details es , wie zu bedienen readAsBinaryString(), readAsDataUrl()und readAsArrayBuffer()für andere Bedürfnisse (zB Bilder, Videos, etc.).

clarkf
quelle
Hey Clark, verstehe ich das richtig? Dadurch wird die hochgeladene Datei gesendet, sobald sie vom Dateisystem in den FileReader-Konstruktor geladen wurde, wobei der einfache .ajax-Handler von jQuery umgangen wird. Dann wird der Rest des Formulars wie gewohnt gesendet?
Joshua Cody
Also gut, ich habe mich vorher in meinem Verständnis geirrt. Jetzt nehme ich die readAsDataUrl eines Bildes, füge sie meiner Datenstruktur in .ajax hinzu und sende alle meine Informationen zusammen. Meine vorherige Lösung umfasste die Standard-Dateieingabeklasse von CodeIgniter, in der Daten aus $ _FILES ['field'] abgerufen wurden. Es sieht also so aus, als müsste ich zum Parsen der base64-Bilddaten zu einer anderen Lösung wechseln. Jeder Rat dazu ist willkommen, da Sie Ihre Antwort hier positiv bewerten. Sobald ich die Implementierung abgeschlossen habe, werde ich sie als richtig markieren.
Joshua Cody
1
@ Joshua Cody - Ich habe die Antwort aktualisiert, um ein wenig mehr Details zu geben. Sie müssen verzeihen, dass ich CodeIgniter in vielen Monden nicht verwendet habe und Ihnen nicht sagen konnte, wie Sie dies in ihre Codebasis integrieren können. Ich bin mir nicht sicher, warum Sie die Datei vor dem Einreichen hochladen müssen, aber dies sollte Ihnen zumindest einen Hinweis geben. (Sie können das Bild auch in eine Datenbank einfügen, wenn dies für Sie besser ist.)
Clarkf
@Clarkf, ich muss vor dem Einreichen nicht hochladen, ich habe dein vorheriges Beispiel falsch verstanden :) Nachdem SO ausgefallen war und ich einige Zeit mit w3 und HTML5Rocks verbracht hatte , begann ich zu verstehen. Ich werde es versuchen und wieder hier sein.
Joshua Cody
Also gut, ich habe den ganzen Morgen damit rumgespielt. PHP scheint schlecht formatierte Dateien zurückzugeben. Sehen Sie zwei Bilder , eines sofort gerendert und das andere nach einem $ _POST an den Server und sofortigem Echo. Ein diff auf den beiden Elementen zeigt dies , dass offenbar PHP alle „+“ Zeichen wird Strippen. Ein str_replace behebt dies für die sofortige Rückgabe, aber die gespeicherte Datei ist immer noch beschädigt und kann nicht über mein Dateisystem geöffnet werden. Fahren Sie fort und markieren Sie dies als korrekt. Vielen Dank für Ihre bisherige Hilfe.
Joshua Cody
6

Mit jQuery (und ohne FormData-API) können Sie Folgendes verwenden:

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!"); });
});

Mit der FormData-API müssen Sie nur alle Felder Ihres Formulars zum FormData-Objekt hinzufügen und über $ .ajax senden ({url: url, data: formData, processData: false, contentType: false, type: "POST"}).

Gheljenor
quelle
1
Diese Lösung behebt nicht die Einschränkung, die XMLHttpRequest.send () den durch sie übertragenen Daten auferlegt. Wenn eine Zeichenfolge (z. B. Ihr mehrteiliges Zeichen) übergeben wird, unterstützt send () keine Binärdaten. Ihr Multipart hier wird als utf-8-Zeichenfolge behandelt und erstickt oder beschädigt Binärdaten, die nicht gültig sind. Utf-8. Wenn Sie FormData wirklich vermeiden müssen, müssen Sie XMLHttpRequest.sendAsBinary () ( Polyfill verfügbar) verwenden . Leider bedeutet dies, dass die Verwendung von jQuery für den Ajax-Aufruf viel schwieriger wird.