Enviando multipart / formdata com jQuery.ajax

563

Eu tenho um problema ao enviar um arquivo para um script PHP ao lado do servidor usando a função ajax do jQuery. É possível obter a lista de arquivos, $('#fileinput').attr('files')mas como é possível enviar esses dados para o servidor? A matriz resultante ( $_POST) no script php-server do servidor é 0 ( NULL) ao usar a entrada de arquivo.

Eu sei que é possível (embora eu não tenha encontrado nenhuma solução jQuery até agora, apenas o código Prototye ( http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html )) )

Isso parece ser relativamente novo; portanto, não mencione o upload de arquivos seria impossível via XHR / Ajax, porque definitivamente está funcionando.

Preciso que a funcionalidade do Safari 5, FF e Chrome seja legal, mas não essencial.

Meu código por enquanto é:

$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});
zoku
fonte
Infelizmente, o uso do objeto FormData não funciona no IE <10.
Alejandro García Iglesias
O @GarciaWebDev supostamente pode usar um polyfill com Flash para oferecer suporte à mesma API. Confira github.com/Modernizr/Modernizr/wiki/… para obter mais informações.
yuxhuang 12/07/2013
Possível duplicado .
Raphael Schweikert 26/03
3
Você pode usar $(':file')para selecionar todos os arquivos de entrada. É um pouco mais simples.
Shahar
@RameshwarVyevhare Essa resposta foi publicada cinco anos após a resposta desta pergunta. Por favor, não trole perguntas semelhantes apenas para promover suas próprias respostas.
Ryan P

Respostas:

882

A partir do Safari 5 / Firefox 4, é mais fácil usar a FormDataclasse:

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

Então agora você tem um FormDataobjeto, pronto para ser enviado junto com o 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);
    }
});

É imperativo que você defina a contentTypeopção false, forçando o jQuery a não adicionar um Content-Typecabeçalho para você, caso contrário, a cadeia de limite estará ausente. Além disso, você deve deixar o processDatasinalizador definido como falso, caso contrário, o jQuery tentará converter seu FormDataem uma string, o que falhará.

Agora você pode recuperar o arquivo no PHP usando:

$_FILES['file-0']

(Existe apenas um arquivo, a file-0menos que você tenha especificado o multipleatributo na entrada do arquivo; nesse caso, os números serão incrementados a cada arquivo.)

Usando a emulação FormData para navegadores mais antigos

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

Criar FormData a partir de um formulário existente

Em vez de iterar manualmente os arquivos, o objeto FormData também pode ser criado com o conteúdo de um objeto de formulário existente:

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

Use uma matriz nativa do PHP em vez de um contador

Apenas nomeie os elementos do arquivo da mesma forma e termine o nome entre colchetes:

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

$_FILES['file']será uma matriz que contém os campos de upload de arquivos para cada arquivo carregado. Na verdade, eu recomendo isso na minha solução inicial, pois é mais simples de repetir.

Raphael Schweikert
fonte
2
Além disso, existe uma emulação FormData que tornará bastante simples a portabilidade dessa solução para navegadores antigos. Tudo o que você precisa fazer é verificar data.fakee definir a contentTypepropriedade manualmente, bem como substituir o xhr para usar sendAsBinary().
Raphael Schweikert 24/08
13
Então você não está usando jQuerynem a FormDataclasse e está me perguntando no contexto de uma pergunta específica para jQuery e uma resposta específica para usar FormData? Sinto muito, mas eu não acho que posso ajudá-lo lá ...
Raphael Schweikert
3
Isso usa application/x-www-form-urlencoded. Existe uma maneira de usar multipart/form-data?
Timmmm
4
@ Timmmm Não, ele usa multipart/form-data. Usar application/x-www-form-urlencodednão funcionaria.
Raphael Schweikert
5
@RoyiNamir Só está documentado em código , receio.
Raphael Schweikert
51

Só queria acrescentar um pouco à grande resposta de Raphael. Veja como obter o PHP para produzir o mesmo $_FILES, independentemente de você usar JavaScript para enviar.

Formulário HTML:

<form enctype="multipart/form-data" action="/test.php" 
method="post" class="putImages">
   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />
</form>

O PHP produz isso $_FILES, quando enviado sem JavaScript:

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
                )

        )

)

Se você fizer aprimoramento progressivo, use o JS do Raphael para enviar os arquivos ...

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

... é assim que a $_FILESmatriz do PHP se parece, depois de usar esse JavaScript para enviar:

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
        )

)

Essa é uma boa matriz e, na verdade, o que algumas pessoas transformam $_FILES, mas acho útil trabalhar com a mesma $_FILES, independentemente se o JavaScript foi usado para enviar. Então, aqui estão algumas pequenas alterações no 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);
});

(Edição de 14 de abril de 2017: removi o elemento de formulário do construtor de FormData () - que corrigiu esse código no Safari.)

Esse código faz duas coisas.

  1. Recupera o inputatributo name automaticamente, tornando o HTML mais sustentável. Agora, desde que forma classe putImages, todo o resto é resolvido automaticamente. Ou seja, a inputnecessidade não precisa ter nenhum nome especial.
  2. O formato da matriz que o HTML normal envia é recriado pelo JavaScript na linha data.append. Observe os colchetes.

Com essas alterações, o envio com JavaScript agora produz exatamente a mesma $_FILESmatriz que o envio com HTML simples.

ajmicek
fonte
Teve o mesmo problema com o Safari. Obrigado pela dica!
Medoingthings
49

Olhe para o meu código, ele faz o trabalho para mim

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );
Asad Malik
fonte
Isso funcionou perfeitamente e é muito simples de implementar.
Jh62 5/03
45

Acabei de criar essa função com base em algumas informações que li.

Use-o como usar .serialize(), basta colocar .serializefiles();.
Trabalhando aqui nos meus testes.

//USAGE: $("#form").serializefiles();
(function($) {
$.fn.serializefiles = function() {
    var obj = $(this);
    /* ADD FILE TO PARAM AJAX */
    var formData = new FormData();
    $.each($(obj).find("input[type='file']"), function(i, tag) {
        $.each($(tag)[0].files, function(i, file) {
            formData.append(tag.name, file);
        });
    });
    var params = $(obj).serializeArray();
    $.each(params, function (i, val) {
        formData.append(val.name, val.value);
    });
    return formData;
};
})(jQuery);
evandro777
fonte
2
Eu estava tentando fazer isso funcionar, mas parecia não reconhecer serializefiles () como uma função, apesar dessa definição estar no topo da página.
Fallenreaper 19/09/12
1
isso funciona para mim muito bem. obtenção de dados com var data = $("#avatar-form").serializefiles();o envio desta via ajax parâmetro de dados e análise com formidável expressa: form.parse(req, function(err, fields, files){obrigado por isso trecho de código :)
SchurigH
23

Se o seu formulário estiver definido no HTML, é mais fácil passar o formulário para o construtor do que iterar e adicionar imagens.

$('#my-form').submit( function(e) {
    e.preventDefault();

    var data = new FormData(this); // <-- 'this' is your form element

    $.ajax({
            url: '/my_URL/',
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',     
            success: function(data){
            ...
Devin Venable
fonte
9

A resposta de Devin Venable estava próxima do que eu queria, mas eu queria uma que funcionasse em vários formulários e usasse a ação já especificada no formulário para que cada arquivo fosse para o lugar certo.

Eu também queria usar o método on () do jQuery para evitar o uso de .ready ().

Isso me levou a isso: (substitua formSelector pelo seu seletor jQuery)

$(document).on('submit', formSelecter, function( e ) {
        e.preventDefault();
    $.ajax( {
        url: $(this).attr('action'),
        type: 'POST',
        data: new FormData( this ),
        processData: false,
        contentType: false
    }).done(function( data ) {
        //do stuff with the data you got back.
    });

});
Karl Henselin
fonte
2

Hoje em dia você nem precisa do jQuery :) buscar a tabela de suporte à API

let result = fetch('url', {method: 'POST', body: new FormData(document.querySelector("#form"))})
Alex Nikulin
fonte
No contexto do upload de arquivos via fetch, consulte a compatibilidade: developer.mozilla.org/pt-BR/docs/Web/API/…
Omar Tariq
@OmarTariq sim eu tenho um link semelhante na minha resposta))
Alex Nikulin
Opa Como posso perder isso:-|
Omar Tariq
1

A classe FormData funciona, no entanto, no iOS Safari (pelo menos no iPhone), não consegui usar a solução de Raphael Schweikert como está.

O Mozilla Dev tem uma boa página sobre como manipular objetos FormData .

Portanto, adicione um formulário vazio em algum lugar da sua página, especificando o enctype:

<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo"></form>

Em seguida, crie o objeto FormData como:

var data = new FormData($("#fileinfo"));

e proceda como no código de Rafael .

topkara
fonte
Eu tive um problema com meus uploads de jquery ajax suspensos silenciosamente no Safari e acabei fazendo um navegador condicional $ ('form-name'). Submit () para Safari em vez do upload de ajax que funciona no IE9 e FF18. Provavelmente não é uma solução ideal para uploads múltiplos, mas eu estava fazendo isso para um único arquivo em um iframe a partir de uma caixa de diálogo jquery, para que funcionasse bem.
glyph
1

Uma dica que encontrei hoje, acho que vale a pena apontar relacionada a esse problema: se o URL da chamada ajax for redirecionado, o cabeçalho do tipo de conteúdo: 'multipart / form-data' pode ser perdido.

Por exemplo, eu estava postando em http://server.com/context?param=x

Na guia de rede do Chrome, vi o cabeçalho de várias partes correto para essa solicitação, mas um redirecionamento 302 para http://server.com/context/?param=x (observe a barra após o contexto)

Durante o redirecionamento, o cabeçalho de várias partes foi perdido. Verifique se as solicitações não estão sendo redirecionadas se essas soluções não estiverem funcionando para você.

James
fonte
1

Todas as soluções acima são boas e elegantes, mas o objeto FormData () não espera nenhum parâmetro, mas usa append () após instancia-lo, como o que foi escrito acima:

formData.append (val.name, val.value);

szatti1489
fonte
1

Se o arquivo de entrada nameindica um array e bandeiras multiple, e você analisar o todo formcom FormData, não é necessário de forma iterativa append()os arquivos de entrada. FormDatamanipulará automaticamente vários arquivos.

$('#submit_1').on('click', function() {
  let data = new FormData($("#my_form")[0]);

  $.ajax({
    url: '/path/to/php_file',
    type: 'POST',
    data: data,
    processData: false,
    contentType: false,
    success: function(r) {
      console.log('success', r);
    },
    error: function(r) {
      console.log('error', r);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="my_form">
  <input type="file" name="multi_img_file[]" id="multi_img_file" accept=".gif,.jpg,.jpeg,.png,.svg" multiple="multiple" />
  <button type="button" name="submit_1" id="submit_1">Not type='submit'</button>
</form>

Observe que um regular button type="button"é usado, não type="submit". Isso mostra que não há dependência do uso submitpara obter essa funcionalidade.

A $_FILESentrada resultante é assim nas ferramentas de desenvolvimento do Chrome:

multi_img_file:
  error: (2) [0, 0]
  name: (2) ["pic1.jpg", "pic2.jpg"]
  size: (2) [1978036, 2446180]
  tmp_name: (2) ["/tmp/phphnrdPz", "/tmp/phpBrGSZN"]
  type: (2) ["image/jpeg", "image/jpeg"]

Nota: Há casos em que algumas imagens são carregadas muito bem quando carregadas como um único arquivo, mas elas falham quando carregadas em um conjunto de vários arquivos. O sintoma é que o PHP relata vazio $_POSTe $_FILESsem o AJAX lançando erros. O problema ocorre com o Chrome 75.0.3770.100 e o PHP 7.0. Apenas parece acontecer com 1 dentre várias dezenas de imagens no meu conjunto de testes.

OXiGEN
fonte
0

As versões anteriores do IE não oferecem suporte ao FormData (a lista completa de suporte do navegador para FormData está aqui: https://developer.mozilla.org/en-US/docs/Web/API/FormData ).

Você pode usar um plug-in jquery (por exemplo, http://malsup.com/jquery/form/#code-samples ) ou usar a solução baseada em IFrame para postar dados de formulário com várias partes por meio do ajax: https: // developer. mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

sudip
fonte
0

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery e formulário HTML

$( "#form" ).bind( "submit", function (e) {

    e.preventDefault();

    $.ajax({

        method: "post",
        url: "process.php",

        data: new FormData( $(this) ), // $(this) = formHTML element

        cache: false,

        /* upload */
        contentType: false,
        processData: false

    });

} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<form id="form" >
    File: <input type="file" name="upload" />
    <input type="submit" value="Submit" />
</form>

antelove
fonte
-1
  1. obter objeto de formulário por jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, os dados são sua vontade
user1909226
fonte
$ ("# id") [0] retorna primeiro nenhum vazio <input type = "file" /> do formulário, como você envia um formulário inteiro, incluindo todos os <input type = "file" /> dele?
Mohammad-Hossein Jamali