用jQuery.ajax发送multipart/formdata

我遇到一个问题,使用jQuery的ajax功能向服务器端的PHP脚本发送一个文件。 用$('#fileinput').attr('files')获得文件列表是可能的,但如何将这些数据发送到服务器上?当使用file-input时,服务器端php脚本的结果数组($_POST)是0(NULL)。

我知道这是有可能的(尽管直到现在我还没有找到任何jQuery的解决方案,只有Prototye的代码(http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html))。

这似乎是比较新的,所以请不要提到文件上传将不可能通过XHR/Ajax,因为它肯定是可以的。

我需要在Safari 5中的功能,FF和Chrome也不错,但不是必须的。

我现在的代码是。

$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});
解决办法

从Safari 5/Firefox 4开始,使用FormData类是最简单的。

var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file-'+i, file);
});

现在你有一个`FormData'对象,准备和XMLHttpRequest一起发送。

jQuery.ajax({
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
});

你必须把contentType选项设置为false,迫使jQuery不为你添加一个Content-Type头,否则,边界字符串就会从里面丢失。 另外,你必须把processData标志设置为false,否则,jQuery将尝试把你的FormData转换成一个字符串,这将会失败。

现在你可以在PHP中使用检索该文件。

$_FILES['file-0']

(只有一个文件,file-0,除非你在文件输入中指定了multiple属性,在这种情况下,数字会随着每个文件的增加而增加)。

使用FormData仿真,适用于旧的浏览器

var opts = {
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
};
if(data.fake) {
    // Make sure no text encoding stuff is done by xhr
    opts.xhr = function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }
    opts.contentType = "multipart/form-data; boundary="+data.boundary;
    opts.data = data.toString();
}
jQuery.ajax(opts);

从现有表单中创建FormData

与其手动迭代文件,还可以用现有表单对象的内容来创建FormData对象。

var data = new FormData(jQuery('form')[0]);

使用PHP本地数组而不是计数器

只要将你的文件元素命名为相同的名字,并在名字后面加上括号。

jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file[]', file);
});

$_FILES['file']将是一个数组,包含每个上传文件的文件上传字段。实际上,我推荐这个方案,而不是我最初的方案,因为它更容易迭代。

评论(31)

只是想对Raphael'的伟大回答做一点补充。这里是如何让PHP产生相同的$_FILES,无论你是否使用JavaScript提交。

HTML表格。


   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />

在不使用JavaScript的情况下提交,PHP会产生这个$_FILES

Array
(
    [media] => Array
        (
            [name] => Array
                (
                    [0] => Galata_Tower.jpg
                    [1] => 518f.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/phpIQaOYo
                    [1] => /tmp/phpJQaOYo
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 258004
                    [1] => 127884
                )

        )

)

如果你做渐进式增强,使用Raphael'的JS来提交文件...

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

$.ajax({
    type: ppiFormMethod,
    data: data,
    url: ppiFormActionURL,
    cache: false,
    contentType: false,
    processData: false,
    success: function(data){
        alert(data);
    }
});

......这是PHP'的$_FILES数组的样子,在使用该JavaScript提交后。

Array
(
    [0] => Array
        (
            [name] => Galata_Tower.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpAQaOYo
            [error] => 0
            [size] => 258004
        )

    [1] => Array
        (
            [name] => 518f.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpBQaOYo
            [error] => 0
            [size] => 127884
        )

)

这是一个很好的数组,实际上有些人把$_FILES转化成了$_FILES,但我发现用同一个$_FILES来工作是很有用的,不管是否用JavaScript来提交。 所以,这里是对JS的一些小改动。

// match anything not a [ or ]
regexp = /^[^[\]]+/;
var fileInput = $('.putImages input[type="file"]');
var fileInputName = regexp.exec( fileInput.attr('name') );

// make files available
var data = new FormData();
jQuery.each($(fileInput)[0].files, function(i, file) {
    data.append(fileInputName+'['+i+']', file);
});

(2017年4月14日编辑:我从FormData()的构造函数中删除了表单元素 -- 这在Safari中修复了这段代码。)

这段代码做了两件事。

1.自动检索input名称属性,使HTML更容易维护。现在,只要form有putImages这个类,其他的事情都会自动处理。也就是说,`input'不需要有任何特殊的名字。 2.正常的HTML提交的数组格式被JavaScript在data.append行中重新创建了。 注意大括号。

有了这些变化,现在用JavaScript提交产生的$_FILES数组与用简单的HTML提交产生的数组完全一样。

评论(1)

我只是根据我读到的一些信息建立了这个功能。

使用它就像使用.serialize(),而只是把.serializefiles();.

评论(2)