ใช้การอัปโหลดไฟล์ HTML5 ด้วย AJAX และ jQuery


84

เป็นที่ยอมรับว่ามีคำถามคล้าย ๆ กันอยู่ใน Stack Overflow แต่ดูเหมือนว่าจะไม่มีอะไรตรงตามความต้องการของฉัน

นี่คือสิ่งที่ฉันต้องการทำ:

  • อัปโหลดรูปแบบข้อมูลทั้งหมดซึ่งเป็นไฟล์เดียว
  • ทำงานกับไลบรารีการอัปโหลดไฟล์ของ Codeigniter

จนถึงที่นี่ทุกอย่างเรียบร้อยดี ข้อมูลเข้าสู่ฐานข้อมูลของฉันตามที่ฉันต้องการ แต่ฉันต้องการส่งแบบฟอร์มของฉันผ่านทางโพสต์ AJAX:

  • ใช้ HTML5 File API ดั้งเดิมไม่ใช่แฟลชหรือโซลูชัน iframe
  • ควรเชื่อมต่อกับ.ajax()เมธอด jQuery ระดับต่ำ

ฉันคิดว่าฉันสามารถจินตนาการได้ว่าจะทำอย่างไรโดยการอัปโหลดไฟล์อัตโนมัติเมื่อค่าของฟิลด์เปลี่ยนไปโดยใช้จาวาสคริปต์บริสุทธิ์ แต่ฉันควรทำทั้งหมดในครั้งเดียวเพื่อส่งใน jQuery ฉันคิดว่ามันเป็นไปไม่ได้ที่จะทำผ่านสตริงการสืบค้นเนื่องจากฉันต้องการส่งผ่านวัตถุไฟล์ทั้งหมด แต่ฉันหลงทางเล็กน้อยในสิ่งที่ต้องทำในตอนนี้

สามารถทำได้หรือไม่?


ฉันไม่รู้เกี่ยวกับส่วน Codeigniter แต่สำหรับส่วน jQuery ลองมองหาปลั๊กอินนี้
BalusC

3
ที่เกี่ยวข้อง: stackoverflow.com/questions/166221/…
Timo Huovinen

คำตอบ:


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 จากระบบไฟล์โดยข้ามตัวจัดการ. Ajax ระดับต่ำของ jQuery แล้วแบบฟอร์มที่เหลือจะยื่นแบบธรรมดาหรือไม่?
Joshua Cody

เอาล่ะฉันผิดในความเข้าใจของฉันมาก่อน ตอนนี้ฉันกำลังอ่าน readAsDataUrl ของรูปภาพเพิ่มลงใน datastring ใน. Ajax และส่งข้อมูลทั้งหมดของฉันพร้อมกัน โซลูชันก่อนหน้าของฉันเกี่ยวข้องกับคลาสอินพุตไฟล์เริ่มต้นของ CodeIgniter ซึ่งดึงข้อมูลจาก $ _FILES ['field'] ดังนั้นดูเหมือนว่าฉันจะต้องเปลี่ยนไปใช้โซลูชันอื่นเพื่อแยกวิเคราะห์ข้อมูลรูปภาพ base64 ยินดีให้คำแนะนำใด ๆ เพิ่มคะแนนคำตอบของคุณที่นี่และเมื่อฉันดำเนินการเสร็จสิ้นฉันจะทำเครื่องหมายว่าถูกต้อง
Joshua Cody

1
@ โจชัวโคดี้ - ฉันอัปเดตคำตอบเพื่อให้รายละเอียดเพิ่มเติมเล็กน้อย คุณจะต้องให้อภัยที่ฉันไม่ได้ใช้ CodeIgniter ในหลายดวงจันทร์และไม่สามารถบอกคุณได้ว่าจะรวมสิ่งนี้เข้ากับ codebase ได้อย่างไร ฉันไม่แน่ใจว่าทำไมคุณต้องอัปโหลดไฟล์ก่อนส่ง แต่อย่างน้อยก็ควรให้เบาะแส (คุณสามารถแทรกรูปภาพลงในฐานข้อมูลได้หากดีกว่าสำหรับคุณ)
clarkf

@Clarkf ฉันไม่จำเป็นต้องอัปโหลดก่อนส่งฉันเข้าใจผิดในตัวอย่างก่อนหน้านี้ของคุณ :) หลังจาก SO ลงไปและฉันใช้เวลากับ w3 และHTML5Rocksฉันก็เริ่มเข้าใจ ฉันจะถ่ายภาพนี้และจะกลับมาที่นี่
Joshua Cody

เอาล่ะยุ่งกับเรื่องนี้ทั้งเช้า ดูเหมือนว่า PHP จะส่งคืนไฟล์ที่มีรูปแบบไม่ถูกต้อง ดูภาพสองภาพภาพหนึ่งแสดงผลทันทีและอีกภาพหนึ่งหลังจาก $ _POST ไปยังเซิร์ฟเวอร์และเสียงสะท้อนทันที ต่างในสององค์ประกอบที่เผยให้เห็นถึงนี้ที่เห็นได้ชัดว่า PHP เป็นปอกทั้งหมด "+" ตัวละคร str_replace แก้ไขสิ่งนี้เพื่อส่งคืนทันที แต่ไฟล์ที่บันทึกไว้ยังเสียหายและไม่สามารถเปิดผ่านระบบไฟล์ของฉันได้ นอกจากนี้ให้ทำเครื่องหมายว่าถูกต้อง ขอบคุณมากสำหรับความช่วยเหลือของคุณจนถึงตอนนี้
Joshua Cody

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, พิมพ์: "POST"})


1
โซลูชันนี้ไม่ได้กล่าวถึงข้อ จำกัด ที่ XMLHttpRequest.send () กำหนดให้กับข้อมูลที่ส่งผ่าน เมื่อส่งผ่านสตริง (เช่นมัลติพาร์ทของคุณ) send () ไม่รองรับข้อมูลไบนารี Multipart ของคุณที่นี่จะถือว่าเป็นสตริง utf-8 และจะสำลักหรือทำให้ข้อมูลไบนารีเสียหายที่ไม่ใช่ utf-8 ที่ถูกต้อง หากคุณต้องการหลีกเลี่ยง FormData จริงๆคุณต้องใช้ XMLHttpRequest.sendAsBinary () ( มี polyfillแต่นั่นหมายความว่าการใช้ jQuery สำหรับการโทร ajax จะยากขึ้นมาก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.