Использование загрузки файлов HTML5 с помощью AJAX и jQuery

84

По общему признанию, в Stack Overflow есть похожие вопросы, но, похоже, ни один из них не полностью соответствует моим требованиям.

Вот что я хочу сделать:

  • Загрузите всю форму данных, одна часть которых представляет собой один файл
  • Работа с библиотекой загрузки файлов Codeigniter

До сих пор все хорошо. Данные попадают в мою базу данных по мере необходимости. Но я также хотел бы отправить свою форму через сообщение AJAX:

  • Использование собственного API файлов HTML5, а не Flash или решения iframe
  • Предпочтительно взаимодействие с .ajax()методом jQuery нижнего уровня

Думаю, я мог представить, как это сделать, автоматически загрузив файл при изменении значения поля с использованием чистого javascript, но я бы предпочел сделать все это одним махом для отправки в jQuery. Я думаю, что это невозможно сделать с помощью строк запроса, поскольку мне нужно передать весь файловый объект, но я немного не понимаю, что делать в этот момент.

Можно ли этого добиться?

Джошуа Коди
источник
Я понятия не имею о части Codeigniter, но для части jQuery поищите этот плагин .
BalusC
3
связанные: stackoverflow.com/questions/166221/…
Тимо Хуовинен

Ответы:

93

Это не так уж сложно. Во-первых, взгляните на интерфейс FileReader .

Итак, когда форма отправлена, поймайте процесс отправки и

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

Затем на стороне сервера (т.е. 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);

Или что-то в этом роде. Может быть , я ошибаюсь (и если я, пожалуйста, поправьте меня), но это должно сохранить файл как - то , как 1287916771myPicture.jpgв /uploads/на сервере, и реагировать с переменной JSON (на continueSubmission()функции) , содержащий имя файла на сервере.

Проверить fwrite()и jQuery.post().

На странице выше подробно описано, как использовать readAsBinaryString(),readAsDataUrl() и readAsArrayBuffer()для других ваших нужд (например, изображений, видео и т. Д.).

Кларк
источник
Привет, Кларк, я правильно понимаю? Это отправит загруженный файл, как только он будет загружен в конструктор FileReader из файловой системы, в обход низкоуровневого обработчика jQuery .ajax. Тогда остальная часть формы будет отправлена ​​как обычно?
Джошуа Коди,
Хорошо, значит, раньше я ошибался в своем понимании. Теперь я беру readAsDataUrl изображения, добавляю его в свою строку данных в .ajax и отправляю всю свою информацию вместе. Мое предыдущее решение включало класс ввода файла по умолчанию CodeIgniter, который собирал данные из $ _FILES ['field'], поэтому похоже, что мне нужно переключиться на другое решение для анализа данных изображения base64. Любые советы по этому поводу приветствуются, голосование за ваш ответ здесь, и как только я закончу реализацию, я отмечу его как правильный.
Джошуа Коди,
1
@Joshua Cody - я обновил ответ, чтобы дать немного больше деталей. Вы должны простить, что я не использовал CodeIgniter много лет и не могу сказать вам, как интегрировать это в их кодовую базу. Я не уверен, почему вам нужно загрузить файл перед отправкой, но это должно хотя бы дать вам представление. (Вы также можете вставить изображение в базу данных, если вам так удобнее).
clarkf
@Clarkf, мне не нужно загружать перед отправкой, я неправильно понял ваш предыдущий пример :) После того, как SO вышел из строя , и я потратил некоторое время на w3 и HTML5Rocks , я начал понимать. Я попробую и вернусь сюда.
Джошуа Коди,
Ладно, все утро возился с этим. Кажется, что PHP возвращает плохо отформатированные файлы. Просматривайте два изображения , одно отображается сразу, а другое - после отправки $ _POST на сервер и немедленного эха. Дифференциал на двух элементов показывает это , что , по- видимому PHP лишает все «+» символы. Str_replace исправляет это для немедленного возврата, но сохраненный файл все еще поврежден и не может быть открыт через мою файловую систему. Кроме того, отметив это как правильное. Большое спасибо за вашу помощь.
Джошуа Коди,
6

С jQuery (и без FormData API) вы можете использовать что-то вроде этого:

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

С FormData API вам просто нужно добавить все поля вашей формы в объект FormData и отправить его через $ .ajax ({url: url, data: formData, processData: false, contentType: false, type: "POST"})

Гельенор
источник
1
Это решение не устраняет ограничение, которое XMLHttpRequest.send () налагает на данные, проходящие через него. При передаче строки (например, вашей multipart) send () не поддерживает двоичные данные. Ваш multipart здесь будет рассматриваться как строка utf-8 и будет подавлять или повреждать двоичные данные, которые не являются допустимыми utf-8. Если вам действительно нужно избегать FormData, вам нужно использовать XMLHttpRequest.sendAsBinary () ( доступен полифилл . К сожалению, это означает, что использование jQuery для вызова ajax становится намного сложнее.