Korzystanie z przesyłania plików HTML5 za pomocą AJAX i jQuery

84

Trzeba przyznać, że na Stack Overflow można znaleźć podobne pytania, ale wydaje się, że żadne z nich nie spełnia moich wymagań.

Oto, co chcę zrobić:

  • Prześlij całą formę danych, z których jeden jest pojedynczym plikiem
  • Pracuj z biblioteką przesyłania plików Codeigniter

Aż do tego miejsca wszystko jest w porządku. Dane trafiają do mojej bazy danych tak, jak tego potrzebuję. Ale chciałbym również przesłać mój formularz pocztą AJAX:

  • Korzystanie z natywnego interfejsu API plików HTML5, a nie technologii Flash lub iframe
  • Najlepiej współpracując z niskopoziomową .ajax()metodą jQuery

Myślę, że mógłbym sobie wyobrazić, jak to zrobić, automatycznie przesyłając plik, gdy wartość pola zmienia się za pomocą czystego javascript, ale wolałbym zrobić to wszystko za jednym zamachem i przesłać w jQuery. Myślę, że nie można tego zrobić za pomocą ciągów zapytań, ponieważ muszę przekazać cały obiekt pliku, ale jestem trochę zagubiony w tym, co mam zrobić w tym momencie.

Czy można to osiągnąć?

Joshua Cody
źródło
Nie mam pojęcia o części Codeigniter, ale w przypadku części jQuery poszukaj tej wtyczki .
BalusC
3
related: stackoverflow.com/questions/166221/…
Timo Huovinen

Odpowiedzi:

93

To nie jest zbyt trudne. Po pierwsze, spójrz na interfejs FileReader .

Po przesłaniu formularza przechwyć proces przesyłania i

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

Następnie po stronie serwera (tj. 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);

Albo coś w tym stylu. Mogę się mylić (a jeśli się mylę, proszę, popraw mnie), ale powinno to zapisać plik jako coś podobnego 1287916771myPicture.jpgdo /uploads/na twoim serwerze i odpowiedzieć za pomocą zmiennej JSON (do continueSubmission()funkcji) zawierającej nazwę pliku na serwerze.

Sprawdź fwrite()i jQuery.post().

Na powyższej stronie szczegółowo opisano, jak używać readAsBinaryString(), readAsDataUrl()i readAsArrayBuffer()do innych potrzeb (np. Obrazów, filmów itp.).

clarkf
źródło
Hej Clark, czy dobrze rozumiem? Spowoduje to wysłanie przesłanego pliku, gdy tylko zostanie załadowany do konstruktora FileReader z systemu plików, pomijając niskopoziomową procedurę obsługi .ajax jQuery. Czy reszta formularza zostanie przesłana normalnie?
Joshua Cody
W porządku, więc wcześniej źle rozumiałem. Teraz pobieram readAsDataUrl obrazu, dodam go do mojego datastringu w .ajax i przesyłam wszystkie moje informacje razem. Moje poprzednie rozwiązanie obejmowało domyślną klasę wejściową pliku CodeIgniter, która pobierała dane z $ _FILES ['field'], więc wygląda na to, że będę musiał przełączyć się na inne rozwiązanie do analizowania danych obrazu base64. Wszelkie porady na ten temat są mile widziane. Głosujcie tutaj za twoją odpowiedzią, a kiedy zakończę wdrażanie, oznaczę ją jako poprawną.
Joshua Cody
1
@Joshua Cody - zaktualizowałem odpowiedź, aby podać trochę więcej szczegółów. Musisz wybaczyć, że nie używałem CodeIgnitera na wielu księżycach i nie mogłem ci powiedzieć, jak zintegrować to z ich bazą kodów. Nie jestem pewien, dlaczego musisz przesłać plik przed przesłaniem, ale powinno to przynajmniej dać ci wskazówkę. (Możesz także wstawić obraz do bazy danych, jeśli to dla ciebie lepsze).
clarkf
@Clarkf, nie muszę przesyłać plików przed przesłaniem, źle zrozumiałem twój poprzedni przykład :) Po tym, jak SO spadło i spędziłem trochę czasu na w3 i HTML5Rocks , zacząłem rozumieć. Dam temu szansę i wrócę tutaj.
Joshua Cody
W porządku, bawiłem się tym przez cały ranek. Wygląda na to, że PHP zwraca źle sformatowane pliki. Zobacz dwa obrazy , jeden renderowany natychmiast, a drugi po $ _POST na serwerze i natychmiastowe echo. Diff na dwóch elementach odsłania to , że najwyraźniej PHP jest usuwanie wszystkich znaków „+”. Str_replace naprawia to w celu natychmiastowego powrotu, ale zapisany plik jest nadal uszkodzony i nie można go otworzyć za pośrednictwem mojego systemu plików. Również idąc dalej i oznaczając to jako poprawne. Bardzo dziękuję za dotychczasową pomoc.
Joshua Cody
6

Z jQuery (i bez FormData API) możesz użyć czegoś takiego:

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

Dzięki FormData API wystarczy dodać wszystkie pola formularza do obiektu FormData i wysłać go przez $ .ajax ({url: url, data: formData, processData: false, contentType: false, type: "POST"})

Gheljenor
źródło
1
To rozwiązanie nie rozwiązuje ograniczenia, które XMLHttpRequest.send () nakłada na dane przesyłane przez nią. Po przekazaniu ciągu (na przykład wieloczęściowego) funkcja send () nie obsługuje danych binarnych. Twój wieloczęściowy tutaj będzie traktowany jako łańcuch utf-8 i będzie dławił się lub uszkadzał dane binarne, które nie są poprawne utf-8. Jeśli naprawdę chcesz uniknąć FormData, musisz użyć XMLHttpRequest.sendAsBinary () ( dostępne polyfill . Niestety oznacza to, że użycie jQuery do wywołania ajax staje się znacznie trudniejsze).