如何使用jQuery发送包含FormData对象的Ajax请求?

570

XMLHttpRequest Level 2标准(仍是工作草案)定义了FormData接口,该接口使得可以将File对象附加到XHR请求(Ajax请求)中。

顺便说一下,这是一个新功能-在过去,“隐藏的iframe技巧”被使用(可以在我的另一个问题中阅读有关此方法的信息)。

以下是其工作方式(示例):

var xhr = new XMLHttpRequest(),
    fd = new FormData();

fd.append( 'file', input.files[0] );
xhr.open( 'POST', 'http://example.com/script.php', true );
xhr.onreadystatechange = handler;
xhr.send( fd );

其中 input 是一个 <input type="file"> 字段,而 handler 是 Ajax 请求的成功处理程序。

在所有浏览器中均可正常工作(除了 IE)。

现在,我想让这个功能能够在 jQuery 中使用。我尝试了以下代码:

var fd = new FormData();    
fd.append( 'file', input.files[0] );

$.post( 'http://example.com/script.php', fd, handler );

很遗憾,这样做是行不通的(会抛出“非法调用”错误-截图在此)。我猜想jQuery期望一个简单的键值对对象来表示表单域名/值,而我传递的FormData实例显然不兼容。

既然可以将FormData实例传递到xhr.send()中,我希望也可以让它与jQuery一起工作。


更新:

我在jQuery的Bug跟踪器上创建了一个“特性票”,地址在这里:http://bugs.jquery.com/ticket/9995

有人建议我使用“Ajax预过滤器”...


更新:

首先,让我演示一下我想要实现的行为。

HTML:

<form>
    <input type="file" id="file" name="file">
    <input type="submit">
</form>

JavaScript:

$( 'form' ).submit(function ( e ) {
    var data, xhr;

    data = new FormData();
    data.append( 'file', $( '#file' )[0].files[0] );

    xhr = new XMLHttpRequest();

    xhr.open( 'POST', 'http://hacheck.tel.fer.hr/xml.pl', true );
    xhr.onreadystatechange = function ( response ) {};
    xhr.send( data );

    e.preventDefault();
});
上述代码会生成这个HTTP请求: multipartformdata 我需要的是 "multipart/form-data" 内容类型!
建议的解决方案如下:
$( 'form' ).submit(function ( e ) {
    var data;

    data = new FormData();
    data.append( 'file', $( '#file' )[0].files[0] );

    $.ajax({
        url: 'http://hacheck.tel.fer.hr/xml.pl',
        data: data,
        processData: false,
        type: 'POST',
        success: function ( data ) {
            alert( data );
        }
    });

    e.preventDefault();
});

然而,这将导致:

wrongcontenttype

正如您所看到的,内容类型是错误的...


1
是的!这是一个好主意!感谢提供信息。我最近找到了一段代码片段可以实现这个功能,但它不是jQuery的,所以我想知道jQuery是否能够实现。对我来说,这更像是一个答案而不是一个问题。请继续更新我们! - Stephen
1
这里也是同样的情况。有更多线索的人可以提供吗? - zaf
@zaf 设置 processData:false 是朝着正确方向迈出的一步(请参考pradeek提出的解决方案)。现在,如果我能手动设置“Content-Type”HTTP请求头……请查看我的更新问题以获取详细信息。 - Šime Vidas
6
你的问题是否已在这里得到回答?https://dev59.com/dG435IYBdhLWcg3weQBy - Oleg Mikheev
嗨,我有一个类似的问题,我正在尝试做这个,但上传应该去到Facebook API页面。我在这里开了一个问题: http://stackoverflow.com/questions/19908342/how-to-upload-video-to-facebook-page-from-a-video-link-via-javascript 感谢您的帮助! - Arturo
显示剩余6条评论
8个回答

1016

我相信你可以这样做:

var fd = new FormData();    
fd.append( 'file', input.files[0] );

$.ajax({
  url: 'http://example.com/script.php',
  data: fd,
  processData: false,
  contentType: false,
  type: 'POST',
  success: function(data){
    alert(data);
  }
});

注:

  • processData设置为false可防止jQuery自动将数据转换为查询字符串。有关更多信息,请参见文档

  • 必须将contentType设置为false,否则jQuery会错误地设置它


不幸的是,这会导致设置错误的“Content-Type”HTTP请求头。请参阅我的问题,详细说明我想要实现的内容以及为什么您当前的解决方案不能满足。也许我可以手动设置Content-Type? - Šime Vidas
12
是的,我相信您可以通过在$.ajax参数中添加键值对来手动设置contentType为'multipart/form-data'。 - pradeek
2
已在Firefox和Chrome中进行了测试。这是解决方案。 - Šime Vidas
2
将processData和contentType设置为false解决了我的问题。谢谢,那真是令人不愉快的经历。 - superluminary
@SalmanPK 如果你正在获取那个URL,你是将它作为GET发送的,这只能作为POST工作。 - ry4nolson
显示剩余8条评论

33

您可以使用以下代码将FormData对象发送到ajax请求中:

$("form#formElement").submit(function(){
    var formData = new FormData($(this)[0]);
});

这与被接受的答案非常相似,但它是针对问题主题的实际答案。这将自动在FormData中提交表单元素,您无需手动将数据附加到FormData变量中。

ajax方法如下所示,

$("form#formElement").submit(function(){
    var formData = new FormData($(this)[0]);
    //append some non-form data also
    formData.append('other_data',$("#someInputData").val());
    $.ajax({
        type: "POST",
        url: postDataUrl,
        data: formData,
        processData: false,
        contentType: false,
        dataType: "json",
        success: function(data, textStatus, jqXHR) {
           //process data
        },
        error: function(data, textStatus, jqXHR) {
           //process error msg
        },
});
你也可以像这样将表单元素手动传递到 FormData 对象中作为参数:
  <p>您还可以手动将表单元素作为参数传递到 FormData 对象中,例如:</p>
var formElem = $("#formId");
var formdata = new FormData(formElem[0]);

希望对你有所帮助。 ;)


13
$(this)[0] 不就是 this 吗? - dfl
为什么$(this)返回一个数组? - halbano
4
$(this) 返回的不是数组,而是可像数组一样迭代的jQuery集合对象。 form[0] 几乎与 form.first() 相同,都只返回第一个真正的HTML元素。 - SteveB

31

还有一些未被提及的技术可供您使用。首先从在ajax参数中设置contentType属性开始。

在pradeek的示例基础上构建:

$('form').submit(function (e) {
    var data;

    data = new FormData();
    data.append('file', $('#file')[0].files[0]);

    $.ajax({
        url: 'http://hacheck.tel.fer.hr/xml.pl',
        data: data,
        processData: false,
        type: 'POST',

        // This will override the content type header, 
        // regardless of whether content is actually sent.
        // Defaults to 'application/x-www-form-urlencoded'
        contentType: 'multipart/form-data', 

        //Before 1.5.1 you had to do this:
        beforeSend: function (x) {
            if (x && x.overrideMimeType) {
                x.overrideMimeType("multipart/form-data");
            }
        },
        // Now you should be able to do this:
        mimeType: 'multipart/form-data',    //Property added in 1.5.1

        success: function (data) {
            alert(data);
        }
    });

    e.preventDefault();
});

在某些情况下,当强制jQuery ajax执行非预期的操作时,beforeSend事件是一个很好的地方。有一段时间人们在1.5.1版jQuery中添加beforeSend之前,使用它来覆盖mimeType。你应该能够在发送前事件中修改jqXHR对象的几乎任何内容。


5
这里存在一个问题:规范要求多部分内容类型必须包括边界参数(明确指出为必需参数)。 - Roman Starkov
1
@romkyns 我是凭记忆编写的。如果您有比我更多的经验/知识,请随时编辑答案!既然您提到了它,快速搜索结果显示:https://dev59.com/sm025IYBdhLWcg3wkGx7 这应该可以让您朝着正确的方向前进。(如果您喜欢,可以从该示例中借鉴并将其合并到上述jQuery中。) - BenSwayne
实际上,目前被接受的答案(我编辑后添加了一个关键修复)最终做到了正确的事情。这很棘手 :) - Roman Starkov
我对这个解决方案抱有很高的期望,但我的 $_FILES 仍然是 NULL... - socca1157

7
我是这样做的,对我很有效,希望这会有所帮助 :)
   <div id="data">
        <form>
            <input type="file" name="userfile" id="userfile" size="20" />
            <br /><br />
            <input type="button" id="upload" value="upload" />
        </form>
    </div>
  <script>
        $(document).ready(function(){
                $('#upload').click(function(){

                    console.log('upload button clicked!')
                    var fd = new FormData();    
                    fd.append( 'userfile', $('#userfile')[0].files[0]);

                    $.ajax({
                      url: 'upload/do_upload',
                      data: fd,
                      processData: false,
                      contentType: false,
                      type: 'POST',
                      success: function(data){
                        console.log('upload success!')
                        $('#data').empty();
                        $('#data').append(data);

                      }
                    });
                });
        });
    </script>   

这与被接受的答案有何不同? - Šime Vidas
fd.append('userfile', $('#userfile')[0].files[0]); 这里需要添加输入文件的ID才能正常工作。 - talalalshehri
这个比其他的更帮助我了。 - vkGunasekaran

5

JavaScript:

function submitForm() {
    var data1 = new FormData($('input[name^="file"]'));
    $.each($('input[name^="file"]')[0].files, function(i, file) {
        data1.append(i, file);
    });

    $.ajax({
        url: "<?php echo base_url() ?>employee/dashboard2/test2",
        type: "POST",
        data: data1,
        enctype: 'multipart/form-data',
        processData: false, // tell jQuery not to process the data
        contentType: false // tell jQuery not to set contentType
    }).done(function(data) {
        console.log("PHP Output:");
        console.log(data);
    });
    return false;
}

PHP:

public function upload_file() {
    foreach($_FILES as $key) {
        $name = time().$key['name'];
        $path = 'upload/'.$name;
        @move_uploaded_file($key['tmp_name'], $path);
    }
}

4

1
如果您想使用ajax提交文件,请使用"jquery.form.js"。这将轻松地提交所有表单元素。

Samples http://jquery.malsup.com/form/#ajaxSubmit

大致看法:

<form id='AddPhotoForm' method='post' action='../photo/admin_save_photo.php' enctype='multipart/form-data'>


<script type="text/javascript">
function showResponseAfterAddPhoto(responseText, statusText)
{ 
    information= responseText;
    callAjaxtolist();
    $("#AddPhotoForm").resetForm();
    $("#photo_msg").html('<div class="album_msg">Photo uploaded Successfully...</div>');        
};

$(document).ready(function(){
    $('.add_new_photo_div').live('click',function(){
            var options = {success:showResponseAfterAddPhoto};  
            $("#AddPhotoForm").ajaxSubmit(options);
        });
});
</script>

1

替换为 - fd.append('file', $('#userfile')[0].files[0]);


9
如果你能解释这两者的区别,我会给你一块饼干。 - ggdx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接