Sử dụng tải lên tệp HTML5 với AJAX và jQuery


84

Phải thừa nhận rằng có những câu hỏi tương tự trên Stack Overflow, nhưng có vẻ như không có câu hỏi nào đáp ứng được yêu cầu của tôi.

Đây là những gì tôi đang tìm cách làm:

  • Tải lên toàn bộ dạng dữ liệu, một phần trong số đó là một tệp duy nhất
  • Làm việc với thư viện tải lên tệp của Codeigniter

Cho đến khi ở đây, tất cả đều tốt. Dữ liệu được đưa vào cơ sở dữ liệu của tôi khi tôi cần. Nhưng tôi cũng muốn gửi biểu mẫu của mình qua một bài đăng AJAX:

  • Sử dụng API tệp HTML5 gốc, không phải flash hoặc giải pháp iframe
  • Tốt hơn là giao tiếp với .ajax()phương thức jQuery cấp thấp

Tôi nghĩ rằng tôi có thể tưởng tượng cách thực hiện điều này bằng cách tự động tải lên tệp khi giá trị của trường thay đổi bằng cách sử dụng javascript thuần túy, nhưng tôi muốn làm tất cả trong một lần để gửi trong jQuery. Tôi nghĩ rằng không thể thực hiện thông qua các chuỗi truy vấn vì tôi cần phải chuyển toàn bộ đối tượng tệp, nhưng tôi hơi mất hứng về việc phải làm vào thời điểm này.

Điều này có thể đạt được không?


Tôi không biết gì về phần Codeigniter, nhưng đối với phần jQuery, hãy xem plugin này .
BalusC

Câu trả lời:


93

Nó không quá khó. Đầu tiên, hãy xem Giao diện FileReader .

Vì vậy, khi biểu mẫu được gửi, hãy nắm bắt quy trình gửi và

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

Sau đó, ở phía máy chủ (tức là 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);

Hoặc một cái gì đó giống như nó. Tôi có thể nhầm lẫn (và nếu đúng, hãy sửa cho tôi), nhưng điều này sẽ lưu trữ tệp dưới dạng một tệp giống như 1287916771myPicture.jpgtrong /uploads/máy chủ của bạn và phản hồi bằng một biến JSON (đối vớicontinueSubmission() hàm) có chứa tên tệp trên máy chủ.

Kiểm tra fwrite()jQuery.post().

Trên trang trên nó Các chi tiết về làm thế nào để sử dụng readAsBinaryString(), readAsDataUrl()readAsArrayBuffer()cho các nhu cầu khác của bạn (ví dụ như hình ảnh, video, vv).


Này Clark, tôi có hiểu đúng không? Thao tác này sẽ gửi tệp đã tải lên ngay sau khi nó được tải vào phương thức khởi tạo FileReader từ hệ thống tệp, bỏ qua trình xử lý .ajax cấp thấp của jQuery. Sau đó, phần còn lại của biểu mẫu sẽ gửi như bình thường?
Joshua Cody

Được rồi, vì vậy tôi đã sai trong sự hiểu biết của mình trước đây. Bây giờ tôi đang lấy readAsDataUrl của một hình ảnh, thêm nó vào chuỗi dữ liệu của tôi trong .ajax và gửi tất cả thông tin của tôi cùng nhau. Giải pháp trước đây của tôi liên quan đến lớp đầu vào tệp mặc định của CodeIgniter lấy dữ liệu từ $ _FILES ['field'], vì vậy có vẻ như tôi sẽ cần chuyển sang một giải pháp khác để phân tích dữ liệu hình ảnh base64. Mọi lời khuyên về điều đó đều được hoan nghênh, tán thành câu trả lời của bạn ở đây và khi tôi hoàn thành việc triển khai, tôi sẽ đánh dấu là đúng.
Joshua Cody

1
@Joshua Cody - Tôi đã cập nhật câu trả lời để cung cấp thêm một chút chi tiết. Bạn sẽ phải tha thứ rằng tôi đã không sử dụng CodeIgniter trong nhiều tuần và không thể cho bạn biết cách tích hợp cái này vào codebase của họ. Tôi không chắc tại sao bạn cần tải tệp lên trước khi gửi, nhưng điều này ít nhất sẽ cung cấp cho bạn manh mối. (Bạn cũng có thể chèn hình ảnh vào cơ sở dữ liệu nếu điều đó tốt hơn cho bạn).
clarkf,

@Clarkf, tôi không cần tải lên trước khi gửi, tôi đã hiểu nhầm ví dụ trước của bạn :) Sau khi SO ngừng hoạt động và tôi dành một chút thời gian cho w3 và HTML5Rocks , tôi bắt đầu hiểu. Tôi sẽ thử và quay lại đây.
Joshua Cody

Được rồi, tôi đã rối tung cả sáng nay. PHP dường như đang trả về các tệp có định dạng sai. Xem hai hình ảnh , một hình ảnh được hiển thị ngay lập tức và hình ảnh còn lại sau $ _POST tới máy chủ và tiếng vọng ngay lập tức. Một khác biệt trên hai phần tử cho thấy điều này , rằng dường như PHP đang loại bỏ tất cả các ký tự "+". Str_replace sửa lỗi này để trả lại ngay lập tức, nhưng tệp đã lưu vẫn bị hỏng và không thể mở được qua hệ thống tệp của tôi. Ngoài ra, hãy tiếp tục và đánh dấu điều này là đúng. Cảm ơn rất nhiều vì sự giúp đỡ của bạn cho đến nay.
Joshua Cody

6

Với jQuery (và không có API FormData), bạn có thể sử dụng một cái gì đó như sau:

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

Với API FormData, bạn chỉ cần thêm tất cả các trường của biểu mẫu vào đối tượng FormData và gửi nó qua $ .ajax ({url: url, data: formData, processData: false, contentType: false, gõ: "POST"})


1
Giải pháp này không giải quyết được hạn chế mà XMLHttpRequest.send () áp đặt cho dữ liệu được truyền qua nó. Khi truyền một chuỗi (chẳng hạn như nhiều phần của bạn), send () không hỗ trợ dữ liệu nhị phân. Phần đa của bạn ở đây sẽ được coi là chuỗi utf-8 và sẽ làm hỏng hoặc làm hỏng dữ liệu nhị phân không hợp lệ utf-8. Nếu bạn thực sự cần phải tránh FormData, bạn cần phải sử dụng XMLHttpRequest.sendAsBinary () ( polyfill sẵn Đáng tiếc là phương tiện này rằng việc sử dụng jQuery cho cuộc gọi ajax trở nên khó khăn hơn..
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.