web-dev-qa-db-fra.com

Empêcher la double soumission de formulaires dans jQuery

J'ai un formulaire qui prend un peu de temps pour que le serveur traite. Je dois m'assurer que l'utilisateur attend et ne tente pas de soumettre à nouveau le formulaire en cliquant à nouveau sur le bouton. J'ai essayé d'utiliser le code jQuery suivant:

<script type="text/javascript">
$(document).ready(function() {
    $("form#my_form").submit(function() {
        $('input').attr('disabled', 'disabled');
        $('a').attr('disabled', 'disabled');
        return true;
    });
});
</script>

Lorsque j'essaie dans Firefox, tout est désactivé, mais le formulaire n'est soumis avec aucune des données POST qu'il est censé inclure. Je ne peux pas utiliser jQuery pour soumettre le formulaire, car le bouton doit être envoyé avec le formulaire car il y a plusieurs boutons de soumission et je détermine lequel a été utilisé pour lequel sa valeur est incluse dans le POST. J'ai besoin que le formulaire soit soumis comme d'habitude et que je doive tout désactiver dès que cela se produit.

Merci!

153
Adam

Mise à jour en 2018: Je viens juste de comprendre quelques points pour cette ancienne réponse et je voulais juste ajouter que la solution best consistait à rendre l'opération idempotente afin que les soumissions en double soient inoffensives.

Par exemple, si le formulaire crée un ordre, mettez un ID unique dans le formulaire. La première fois que le serveur voit une demande de création de commande avec cet identifiant, il doit la créer et répondre "avec succès". Les soumissions ultérieures doivent également répondre "succès" (au cas où le client n'aurait pas reçu la première réponse) mais ne devraient rien changer.

Les doublons doivent être détectés via une vérification de l'unicité dans la base de données afin d'éviter les situations de concurrence.


Je pense que votre problème est cette ligne:

$('input').attr('disabled','disabled');

Vous désactivez TOUTES les entrées, y compris celles dont les données sont censées être soumises par le formulaire. 

Pour désactiver uniquement le (s) bouton (s) d'envoi, vous pouvez procéder comme suit:

$('button[type=submit], input[type=submit]').prop('disabled',true);

Cependant, je ne pense pas que IE soumette le formulaire si même ces boutons sont désactivés. Je suggérerais une approche différente.

Un plugin jQuery pour le résoudre

Nous venons de résoudre ce problème avec le code suivant. Le truc ici consiste à utiliser la fonction data() de jQuery pour marquer le formulaire comme déjà soumis ou non. De cette façon, nous n’avons pas à nous soucier des boutons de soumission, ce qui effraie IE.

// jQuery plugin to prevent double submission of forms
jQuery.fn.preventDoubleSubmission = function() {
  $(this).on('submit',function(e){
    var $form = $(this);

    if ($form.data('submitted') === true) {
      // Previously submitted - don't submit again
      e.preventDefault();
    } else {
      // Mark it so that the next submit can be ignored
      $form.data('submitted', true);
    }
  });

  // Keep chainability
  return this;
};

Utilisez-le comme ceci:

$('form').preventDoubleSubmission();

S'il y a AJAX formulaires que devrait être autorisés à soumettre plusieurs fois par chargement de page, vous pouvez leur attribuer une classe l'indiquant, puis les exclure de votre sélecteur de la manière suivante:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
295
Nathan Long

Le choix du moment est incorrect - comment savoir combien de temps l’action durera sur le navigateur du client?

Comment faire

$('form').submit(function(){
  $(this).find(':submit').attr('disabled','disabled');
});

Lorsque le formulaire est soumis, il désactive tous les boutons de soumission à l'intérieur.

N'oubliez pas que dans Firefox, lorsque vous désactivez un bouton, cet état sera mémorisé lorsque vous revenez dans l'historique. Pour éviter cela, vous devez activer les boutons lors du chargement de la page, par exemple.

41
Ctrl-C

Je pense que la réponse de Nathan Long est la voie à suivre. Pour moi, j'utilise la validation côté client, alors je viens d'ajouter une condition selon laquelle le formulaire doit être valide.

EDIT: Si ceci n'est pas ajouté, l'utilisateur ne pourra jamais soumettre le formulaire si la validation côté client rencontre une erreur.

        // jQuery plugin to prevent double submission of forms
        jQuery.fn.preventDoubleSubmission = function () {
            $(this).on('submit', function (e) {
                var $form = $(this);

                if ($form.data('submitted') === true) {
                    // Previously submitted - don't submit again
                    alert('Form already submitted. Please wait.');
                    e.preventDefault();
                } else {
                    // Mark it so that the next submit can be ignored
                    // ADDED requirement that form be valid
                    if($form.valid()) {
                        $form.data('submitted', true);
                    }
                }
            });

            // Keep chainability
            return this;
        };
19
PTK

event.timeStamp ne fonctionne pas dans Firefox. Si vous renvoyez false, ce n'est pas standard, vous devriez appeler event.preventDefault(). Et pendant que nous y sommes, always utilise des accolades avec une construction de contrôle

Pour résumer toutes les réponses précédentes, voici un plugin qui fait le travail et fonctionne sur plusieurs navigateurs. 

jQuery.fn.preventDoubleSubmission = function() {

    var last_clicked, time_since_clicked;

    jQuery(this).bind('submit', function(event) {

        if(last_clicked) {
            time_since_clicked = jQuery.now() - last_clicked;
        }

        last_clicked = jQuery.now();

        if(time_since_clicked < 2000) {
            // Blocking form submit because it was too soon after the last submit.
            event.preventDefault();
        }

        return true;
    });
};

Pour adresser Kern3l, la méthode de synchronisation fonctionne pour moi tout simplement parce que nous essayons d’arrêter un double-clic sur le bouton d'envoi. Si le temps de réponse d’une soumission est très long, je vous recommande de remplacer le bouton ou le formulaire de soumission par un bouton fléché.

Bloquer complètement les soumissions ultérieures du formulaire, comme le font la plupart des exemples ci-dessus, a un mauvais effet fabriqué. Cela ferait certainement un utilisateur en colère.

9
Robin Daugherty

Veuillez vérifier jquery-safeform plugin. 

Exemple d'utilisation:

$('.safeform').safeform({
    timeout: 5000,  // disable next submission for 5 sec
    submit: function() {
        // You can put validation and ajax stuff here...

        // When done no need to wait for timeout, re-enable the form ASAP
        $(this).safeform('complete');
        return false;
    }
});
8
Max Kamenkov

Il est possible d'améliorer l'approche de Nathan Long . Vous pouvez remplacer la logique de détection du formulaire déjà soumis par celle-ci:

var lastTime = $(this).data("lastSubmitTime");
if (lastTime && typeof lastTime === "object") {
    var now = new Date();
    if ((now - lastTime) > 2000) // 2000ms
        return true;
    else
        return false;
}
$(this).data("lastSubmitTime", new Date());
return true; // or do an ajax call or smth else
4
Mikhail Fiadosenka

Le code de Nathan mais pour jQuery Validate plugin

Si vous utilisez le plug-in jQuery Validate, le gestionnaire d'envoi est déjà implémenté. Dans ce cas, il n'y a aucune raison d'en implémenter plus d'un. Le code:

jQuery.validator.setDefaults({
  submitHandler: function(form){
    // Prevent double submit
    if($(form).data('submitted')===true){
      // Previously submitted - don't submit again
      return false;
    } else {
      // Mark form as 'submitted' so that the next submit can be ignored
      $(form).data('submitted', true);
      return true;
    }
  }
});

Vous pouvez facilement l'étendre dans le bloc } else {- pour désactiver les entrées et/ou le bouton d'envoi.

À votre santé

4
Alph.Dev

... mais le formulaire n'est pas soumis avec l’une des données POST à laquelle il est censé s’appliquer comprendre.

Correct. Les noms/valeurs d'éléments de formulaire désactivés ne seront pas envoyés au serveur. Vous devriez les définir comme readonly elements.

De plus, les ancres ne peuvent pas être désactivées comme ça. Vous devrez supprimer leurs HREF (non recommandé) ou empêcher leur comportement par défaut (meilleur moyen), par exemple:

<script type="text/javascript">
$(document).ready(function(){
    $("form#my_form").submit(function(){
      $('input').attr('readonly', true);
      $('input[type=submit]').attr("disabled", "disabled");
      $('a').unbind("click").click(function(e) {
          e.preventDefault();
          // or return false;
      });
    });
</script>
4
karim79

J'ai fini par utiliser les idées de ce billet pour proposer une solution assez similaire à la version d'AtZako.

 jQuery.fn.preventDoubleSubmission = function() {

    var last_clicked, time_since_clicked;

    $(this).bind('submit', function(event){

    if(last_clicked) 
      time_since_clicked = event.timeStamp - last_clicked;

    last_clicked = event.timeStamp;

    if(time_since_clicked < 2000)
      return false;

    return true;
  });   
};

Utilisant comme ceci: 

$('#my-form').preventDoubleSubmission();

J'ai constaté que les solutions qui n'incluaient pas de délai d'expiration, mais uniquement une soumission ou des éléments de formulaire désactivés, posaient des problèmes, car une fois le verrouillage déclenché, vous ne pouvez pas soumettre à nouveau tant que vous n'avez pas actualisé la page. Cela me pose quelques problèmes lorsque je fais des choses en ajax.

Cela peut probablement être un peu loué, car ce n’est pas si fantastique.

2
jacklin

La solution de Nathan modifiée un peu pour Bootstrap 3. Cela définira un texte de chargement sur le bouton d'envoi. En outre, il expirera après 30 secondes et permettra au formulaire d'être soumis à nouveau.

jQuery.fn.preventDoubleSubmission = function() {
  $('input[type="submit"]').data('loading-text', 'Loading...');

  $(this).on('submit',function(e){
    var $form = $(this);

    $('input[type="submit"]', $form).button('loading');

    if ($form.data('submitted') === true) {
      // Previously submitted - don't submit again
      e.preventDefault();
    } else {
      // Mark it so that the next submit can be ignored
      $form.data('submitted', true);
      $form.setFormTimeout();
    }
  });

  // Keep chainability
  return this;
};

jQuery.fn.setFormTimeout = function() {
  var $form = $(this);
  setTimeout(function() {
    $('input[type="submit"]', $form).button('reset');
    alert('Form failed to submit within 30 seconds');
  }, 30000);
};
2
AlexZ

Si vous utilisezAJAXpour publier un formulaire, définissez async: false pour éviter les envois supplémentaires avant que le formulaire n'efface:

$("#form").submit(function(){
    var one = $("#one").val();
    var two = $("#two").val();
    $.ajax({
      type: "POST",
      async: false,  // <------ Will complete submit before allowing further action
      url: "process.php",
      data: "one="+one+"&two="+two+"&add=true",
      success: function(result){
        console.log(result);
        // do something with result
      },
      error: function(){alert('Error!')}
    });
    return false;
   }
});
2
Al Kari

voici comment je le fais:

$(document).ready(function () {
  $('.class_name').click(function () {
    $(this).parent().append('<img src="data:image/gif;base64,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" />');
    $(this).hide();
  });
});

crédits: https://github.com/phpawy/jquery-submit-once

1
phpawy

J'ai eu des problèmes similaires et mes solutions sont les suivantes.

Si vous ne possédez aucune validation côté client, vous pouvez simplement utiliser la méthode jquery one () comme indiqué ici.

http://api.jquery.com/one/

Cela désactive le gestionnaire après son invocation.

$("#mysavebuttonid").on("click", function () {
  $('form').submit();
});

Si vous procédez à la validation côté client comme je le faisais, il est un peu plus compliqué. L'exemple ci-dessus ne vous permettra pas de soumettre à nouveau après l'échec de la validation. Essayez cette approche à la place

$("#mysavebuttonid").on("click", function (event) {
  $('form').submit();
  if (boolFormPassedClientSideValidation) {
        //form has passed client side validation and is going to be saved
        //now disable this button from future presses
        $(this).off(event);
   }
});
1
mattbloke

Utilisez deux boutons d'envoi.

<input id="sub" name="sub" type="submit" value="OK, Save">
<input id="sub2" name="sub2" type="submit" value="Hidden Submit" style="display:none">

Et jQuery:

$("#sub").click(function(){
  $(this).val("Please wait..");
  $(this).attr("disabled","disabled");
  $("#sub2").click();
});
1
izmir_LEE

Changer le bouton d'envoi:

<input id="submitButtonId" type="submit" value="Delete" />

Avec bouton normal:

<input id="submitButtonId" type="button" value="Delete" />

Puis utilisez la fonction click:

$("#submitButtonId").click(function () {
        $('#submitButtonId').prop('disabled', true);
        $('#myForm').submit();
    });

Et rappelez-vous le bouton de réactivation lorsque cela est nécessaire:

$('#submitButtonId').prop('disabled', false);
0
Dani

Je n'arrive pas à croire le bon vieux tour en css des événements de pointeur: aucun n'a encore été mentionné. J'ai eu le même problème en ajoutant un attribut désactivé, mais cela ne renvoie pas. Essayez ce qui suit et remplacez #SubmitButton par l'ID de votre bouton d'envoi.

$(document).on('click', '#SubmitButton', function () {
    $(this).css('pointer-events', 'none');
})
0
Tom McDonough

Pourquoi ne pas simplement ceci - cela soumettra le formulaire mais désactivera également le bouton de soumission,

   $('#myForm').on('submit', function(e) {
       var clickedSubmit = $(this).find('input[type=submit]:focus');
       $(clickedSubmit).prop('disabled', true);
   });

De plus, si vous utilisez jQuery Validate, vous pouvez placer ces deux lignes sous if ($('#myForm').valid()).

0
gene b.

Ma solution:

// jQuery plugin to prevent double submission of forms
$.fn.preventDoubleSubmission = function () {
    var $form = $(this);

    $form.find('[type="submit"]').click(function () {
        $(this).prop('disabled', true);
        $form.submit();
    });

    // Keep chainability
    return this;
};
0
user1672591

Vous pouvez arrêter la seconde soumission par cette

$("form").submit(function() {
        // submit more than once return false
        $(this).submit(function() {
            return false;
        });
        // submit once return true
        return true; // or do what you want to do
    });
});
0
Mohammed Zayan

Utilisez simple compteur sur soumettre.

    var submitCounter = 0;
    function monitor() {
        submitCounter++;
        if (submitCounter < 2) {
            console.log('Submitted. Attempt: ' + submitCounter);
            return true;
        }
        console.log('Not Submitted. Attempt: ' + submitCounter);
        return false;
    }

Et appelez la fonction monitor() pour soumettre le formulaire.

    <form action="/someAction.go" onsubmit="return monitor();" method="POST">
        ....
        <input type="submit" value="Save Data">
    </form>
0
Mr. Mak

Dans mon cas, onsubmit du formulaire avait un code de validation, donc j'incrémente Nathan Long answer avec un point de contrôle onsubmit 

$.fn.preventDoubleSubmission = function() {
      $(this).on('submit',function(e){
        var $form = $(this);
        //if the form has something in onsubmit
        var submitCode = $form.attr('onsubmit');
        if(submitCode != undefined && submitCode != ''){
            var submitFunction = new Function (submitCode);
            if(!submitFunction()){
                event.preventDefault();
                return false;
            }                   
        }

        if ($form.data('submitted') === true) {
            /*Previously submitted - don't submit again */
            e.preventDefault();
        } else {
          /*Mark it so that the next submit can be ignored*/
          $form.data('submitted', true);
        }
      });

      /*Keep chainability*/
      return this;
    };
0
bocapio

ce code affichera le chargement sur l’étiquette du bouton, et définira le bouton sur

désactiver l'état, puis après le traitement, réactiver et retourner le texte du bouton d'origine **

$(function () {

    $(".btn-Loading").each(function (idx, Elm) {
        $(Elm).click(function () {
            //do processing
            if ($(".input-validation-error").length > 0)
                return;
            $(this).attr("label", $(this).text()).text("loading ....");
            $(this).delay(1000).animate({ disabled: true }, 1000, function () {
                //original event call
                $.when($(Elm).delay(1000).one("click")).done(function () {
                    $(this).animate({ disabled: false }, 1000, function () {
                        $(this).text($(this).attr("label"));
                    })
                });
                //processing finalized
            });
        });
    });
    // and fire it after definition
}

)

0
Mohamed.Abdo