Envoi de plusieurs parties / formulaires avec jQuery.ajax


563

J'ai un problème lors de l'envoi d'un fichier vers un script PHP côté serveur à l'aide de la fonction ajax de jQuery. Il est possible d'obtenir la liste de fichiers avec $('#fileinput').attr('files')mais comment est-il possible d'envoyer ces données au serveur? Le tableau résultant ( $_POST) sur le script php côté serveur est 0 ( NULL) lors de l'utilisation de l'entrée de fichier.

Je sais que c'est possible (même si je n'ai trouvé aucune solution jQuery jusqu'à présent, seul le code Prototye ( http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html ) ).

Cela semble être relativement nouveau, alors veuillez ne pas mentionner que le téléchargement de fichiers serait impossible via XHR / Ajax, car cela fonctionne certainement.

J'ai besoin des fonctionnalités de Safari 5, FF et Chrome serait bien mais ne sont pas essentielles.

Mon code pour l'instant est:

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

Malheureusement, l'utilisation de l'objet FormData ne fonctionne pas sur IE <10.
Alejandro García Iglesias

@GarciaWebDev soi-disant vous pouvez utiliser un polyfill avec Flash pour prendre en charge la même API. Consultez github.com/Modernizr/Modernizr/wiki/… pour plus d'informations.
yuxhuang


3
Vous pouvez utiliser $(':file')pour sélectionner tous les fichiers d'entrée. C'est juste un peu plus simple.
Shahar

@RameshwarVyevhare Cette réponse a été publiée cinq ans après la réponse à cette question. Veuillez ne pas troll des questions similaires juste pour promouvoir vos propres réponses.
Ryan P

Réponses:


882

À partir de Safari 5 / Firefox 4, il est plus simple d'utiliser la FormDataclasse:

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

Alors maintenant, vous avez un FormDataobjet, prêt à être envoyé avec 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);
    }
});

Il est impératif que vous définissiez l' contentTypeoption sur false, forçant jQuery à ne pas ajouter d'en- Content-Typetête pour vous, sinon, la chaîne de limite sera manquante. En outre, vous devez laisser l' processDataindicateur défini sur false, sinon, jQuery essaiera de convertir votre FormDataen chaîne, ce qui échouera.

Vous pouvez maintenant récupérer le fichier en PHP en utilisant:

$_FILES['file-0']

(Il n'y a qu'un seul fichier, file-0sauf si vous avez spécifié l' multipleattribut sur votre entrée de fichier, auquel cas, les nombres incrémenteront avec chaque fichier.)

Utilisation de l' émulation FormData pour les anciens navigateurs

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

Créer FormData à partir d'un formulaire existant

Au lieu d'itérer manuellement les fichiers, l'objet FormData peut également être créé avec le contenu d'un objet de formulaire existant:

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

Utilisez un tableau natif PHP au lieu d'un compteur

Nommez simplement vos éléments de fichier de la même manière et terminez le nom entre crochets:

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

$_FILES['file']sera alors un tableau contenant les champs de téléchargement de fichier pour chaque fichier téléchargé. En fait, je recommande cela par rapport à ma solution initiale car il est plus simple de répéter.


2
En outre, il existe une émulation FormData qui rendra le portage de cette solution vers des navigateurs plus anciens assez simple. Tout ce que vous avez à faire est de vérifier data.fakeet de définir la contentTypepropriété manuellement ainsi que de remplacer xhr à utiliser sendAsBinary().
Raphael Schweikert

13
Vous n'utilisez donc jQueryni la FormDataclasse et vous me posez la question dans le cadre d'une question spécifique à jQuery et d'une réponse spécifique à l'utilisation de FormData? Je suis désolé mais je ne pense pas pouvoir vous y aider…
Raphael Schweikert

3
Cela utilise application/x-www-form-urlencoded. Y a-t-il un moyen d'utiliser à la multipart/form-dataplace?
Timmmm

4
@Timmmm Non, il utilise multipart/form-data. L'utilisation application/x-www-form-urlencodedne fonctionnerait pas.
Raphael Schweikert

5
@RoyiNamir C'est seulement documenté dans le code , je le crains.
Raphael Schweikert du

51

Je voulais juste ajouter un peu à la grande réponse de Raphaël. Voici comment obtenir que PHP produise le même $_FILES, que vous utilisiez ou non JavaScript pour soumettre.

Formulaire 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>

PHP produit ceci $_FILES, lorsqu'il est soumis sans 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
                )

        )

)

Si vous effectuez une amélioration progressive, en utilisant le JS de Raphaël pour soumettre les fichiers ...

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

... voici à quoi $_FILESressemble le tableau de PHP , après avoir utilisé ce JavaScript pour soumettre:

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
        )

)

C'est un bon tableau, et en fait ce que certaines personnes transforment $_FILES, mais je trouve utile de travailler avec le même $_FILES, peu importe si JavaScript a été utilisé pour soumettre. Voici donc quelques modifications mineures apportées au 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);
});

(Édition du 14 avril 2017: j'ai supprimé l'élément de formulaire du constructeur de FormData () - qui a corrigé ce code dans Safari.)

Ce code fait deux choses.

  1. Récupère inputautomatiquement l'attribut name, ce qui rend le HTML plus facile à gérer. Maintenant, aussi longtemps que formla classe putImages, tout le reste est pris en charge automatiquement. Autrement dit, le inputbesoin n'a pas de nom spécial.
  2. Le format de tableau que le HTML normal soumet est recréé par le JavaScript dans la ligne data.append. Notez les crochets.

Avec ces modifications, la soumission avec JavaScript produit désormais exactement le même $_FILEStableau que la soumission avec du HTML simple.


Eu le même problème avec Safari. Merci pour l'astuce!
medoingthings

49

Regardez mon code, il fait le travail pour moi

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );

Cela a fonctionné parfaitement et c'est très simple à mettre en œuvre.
Jh62

45

Je viens de créer cette fonction en fonction des informations que j'ai lues.

Utilisez-le comme en utilisant .serialize(), au lieu de cela, mettez simplement .serializefiles();.
Travailler ici dans mes tests.

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

2
J'essayais de faire fonctionner cela, mais il semblait ne pas reconnaître serializefiles () en tant que fonction, malgré cette définition en haut de la page.
Fallenreaper

1
cela fonctionne très bien pour moi. obtenir des données en var data = $("#avatar-form").serializefiles();envoyant cela via le paramètre de données ajax et en analysant avec express formidable: form.parse(req, function(err, fields, files){merci pour cet extrait de code :)
SchurigH

23

Si votre formulaire est défini dans votre code HTML, il est plus facile de passer le formulaire dans le constructeur que d'itérer et d'ajouter des images.

$('#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){
            ...

9

La réponse de Devin Venable était proche de ce que je voulais, mais je voulais une qui fonctionnerait sur plusieurs formulaires et utiliserait l'action déjà spécifiée dans le formulaire pour que chaque fichier aille au bon endroit.

Je voulais également utiliser la méthode on () de jQuery pour éviter d'utiliser .ready ().

Cela m'a amené à ceci: (remplacez formSelector par votre sélecteur 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.
    });

});


1

La classe FormData fonctionne, mais dans iOS Safari (sur l'iPhone au moins), je n'ai pas pu utiliser la solution de Raphael Schweikert telle quelle.

Mozilla Dev a une belle page sur la manipulation d'objets FormData .

Alors, ajoutez un formulaire vide quelque part dans votre page, en spécifiant le enctype:

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

Ensuite, créez un objet FormData en tant que:

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

et procédez comme dans le code de Raphaël .


J'ai eu un problème avec mes téléchargements ajax jquery suspendus en silence dans Safari et j'ai fini par faire un navigateur conditionnel $ ('form-name'). Submit () pour Safari au lieu du téléchargement ajax qui fonctionne dans IE9 et FF18. Probablement pas une solution idéale pour les téléchargements multiples, mais je faisais cela pour un seul fichier dans une iframe à partir d'une boîte de dialogue jquery, donc cela fonctionnait bien.
glyphe

1

Un problème que j'ai rencontré aujourd'hui, je pense qu'il vaut la peine de le souligner en rapport avec ce problème: si l'URL de l'appel ajax est redirigée, l'en-tête du type de contenu: 'multipart / form-data' peut être perdu.

Par exemple, je publiais sur http://server.com/context?param=x

Dans l'onglet réseau de Chrome, j'ai vu l'en-tête multipartie correct pour cette demande, mais ensuite une redirection 302 vers http://server.com/context/?param=x (notez la barre oblique après le contexte)

Pendant la redirection, l'en-tête en plusieurs parties a été perdu. Assurez-vous que les demandes ne sont pas redirigées si ces solutions ne fonctionnent pas pour vous.


1

Toutes les solutions ci-dessus sont bonnes et élégantes, mais l'objet FormData () n'attend aucun paramètre, mais utilisez append () après l'instanciation, comme ce que l'on a écrit ci-dessus:

formData.append (nom.val, valeur.val);


1

Si l'entrée de fichier nameindique un tableau et des drapeaux multiple, et que vous analysez le tout formavec FormData, il n'est pas nécessaire d'itérer de manière itérative append()les fichiers d'entrée. FormDatagérera automatiquement plusieurs fichiers.

$('#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>

Notez qu'un régulier button type="button"est utilisé, non type="submit". Cela montre qu'il n'y a aucune dépendance à utiliser submitpour obtenir cette fonctionnalité.

L' $_FILESentrée résultante est comme ceci dans les outils de développement 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"]

Remarque: Il y a des cas où certaines images se téléchargent très bien lorsqu'elles sont téléchargées en tant que fichier unique, mais elles échouent lorsqu'elles sont téléchargées dans un ensemble de plusieurs fichiers. Le symptôme est que les rapports PHP sont vides $_POSTet $_FILESsans AJAX ne génère aucune erreur. Le problème se produit avec Chrome 75.0.3770.100 et PHP 7.0. Semble seulement se produire avec 1 sur plusieurs dizaines d'images dans mon jeu de test.


0

Les anciennes versions d'IE ne prennent pas en charge FormData (la liste complète de prise en charge du navigateur pour FormData est ici: https://developer.mozilla.org/en-US/docs/Web/API/FormData ).

Soit vous pouvez utiliser un plugin jquery (par exemple, http://malsup.com/jquery/form/#code-samples ), soit vous pouvez utiliser une solution basée sur IFrame pour publier des données de formulaire en plusieurs parties via ajax: https: // developer. mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript


0

PHP

<?php 
    var_dump( $_FILES ); 
?>

jQuery et formulaire 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>


-1
  1. obtenir un objet de formulaire par jquery-> $ ("# id") [0]
  2. data = new FormData ($ ("# id") [0]);
  3. ok, les données sont votre besoin

$ ("# id") [0] ne renvoie en premier aucun vide <input type = "file" /> du formulaire, comment soumettez-vous le formulaire en entier, y compris tous les <input type = "file" /> de celui-ci?
Mohammad-Hossein Jamali
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.