Empêcher la double soumission de formulaires dans jQuery

170

J'ai un formulaire qui prend un peu de temps pour que le serveur soit traité. Je dois m'assurer que l'utilisateur attend et n'essaye 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>

Quand j'essaye ceci 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 j'ai besoin que le bouton soit soumis avec le formulaire car il y a plusieurs boutons d'envoi et je détermine lequel a été utilisé par lequel sa valeur est incluse dans le POST. J'ai besoin que le formulaire soit soumis comme d'habitude et je dois tout désactiver juste après que cela se produise.

Merci!

Adam
la source
Merci à tous pour votre aide!
Adam

Réponses:

315

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

Par exemple, si le formulaire crée une commande, mettez un identifiant 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 «succès». Les soumissions suivantes devraient également répondre «succès» (au cas où le client n'obtiendrait pas la première réponse) mais ne devraient rien changer.

Les doublons doivent être détectés via un contrôle d'unicité dans la base de données pour éviter les conditions de concurrence.


Je pense que votre problème est cette ligne:

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

Vous désactivez TOUTES les entrées, y compris, je suppose, celles dont le formulaire est censé soumettre les données.

Pour désactiver uniquement le (s) bouton (s) d'envoi, vous pouvez faire ceci:

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

Cependant, je ne pense pas que IE soumettra 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. L'astuce ici consiste à utiliser jQuery data()pour marquer le formulaire comme déjà soumis ou non. De cette façon, nous n'avons pas à jouer avec les boutons d'envoi, 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 des formulaires AJAX qui devraient être autorisés à soumettre plusieurs fois par chargement de page, vous pouvez leur donner une classe indiquant cela, puis les exclure de votre sélecteur comme ceci:

$('form:not(.js-allow-double-submission)').preventDoubleSubmission();
Nathan Long
la source
2
Juste un pointeur - ne serait-il pas préférable de mettre en cache le $(this)sur un var that = $(this)?
Stuart.Sklinar
5
@ Stuart.Sklinar - bonne idée. J'ai $formcependant utilisé - 'form' parce que c'est plus descriptif, et le premier $parce que par ma convention, cela signifie que c'est un objet jQuery.
Nathan Long
21
Lorsque vous utilisez jquery, validez la validation discrète, mieux vaut vérifier si le formulaire est valide. if ($ (this) .valid)) {$ form.data ('soumis', true);}
cck
1
@cck Awesome, j'ai utilisé ça. Vous aviez un paren de fermeture supplémentaire que j'ai supprimé: if ($ (this) .valid) {$ form.data ('soumis', true);
Brian MacKay
1
@BrianMacKay si le formulaire n'est pas valide et que le bouton d'envoi est enfoncé, ce plugin jquery marque l'attribut soumis comme vrai mais une validation discrète empêche la soumission en raison d'erreurs de validation. Après avoir corrigé les erreurs, on ne peut pas soumettre le formulaire car l'attribut soumis est vrai!
cck
44

L'approche de synchronisation est incorrecte - 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ésactivera tous les boutons d'envoi à l'intérieur.

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

Ctrl-C
la source
2
+1 à cause du conseil de Firefox, mais existe-t-il une autre option que l'activation des boutons lors du chargement de la page?
Raphael Isidro
1
C'est la manière la plus propre de procéder. J'essayais de désactiver le bouton d'envoi avec un gestionnaire d'événements de clic, mais mon approche causait des problèmes avec Chrome. Cette solution fonctionne à merveille.
Anthony au
1
Soyez très prudent avec cette approche, si vous avez une valeur sur le bouton d'envoi et que vous en avez besoin, le formulaire ne la soumettra pas.
Fabien Ménager le
Selon la dernière documentation jQuery, vous devriez utiliser .prop au lieu de .attr. Référence: api.jquery.com/prop/#entry-longdesc-1 "La méthode .prop () doit être utilisée pour définir désactivé et coché au lieu de la méthode .attr ()."
J Plana
19

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

EDIT : Si cela 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;
        };
PTK
la source
9

event.timeStampne fonctionne pas dans Firefox. Renvoyer false n'est pas standard, vous devez appeler event.preventDefault(). Et tant que nous y sommes, utilisez toujours 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 avec 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 aborder Kern3l, la méthode de synchronisation fonctionne pour moi simplement parce que nous essayons d'arrêter un double-clic sur le bouton d'envoi. Si vous avez un temps de réponse très long à une soumission, je vous recommande de remplacer le bouton ou le formulaire d'envoi par un spinner.

Le blocage complet des soumissions ultérieures du formulaire, comme le font la plupart des exemples ci-dessus, a un mauvais effet secondaire: s'il y a une défaillance du réseau et qu'ils veulent essayer de soumettre à nouveau, ils ne pourraient pas le faire et perdraient les modifications qu'ils fabriqué. Cela rendrait certainement un utilisateur en colère.

Robin Daugherty
la source
2
Vous avez raison. Vous pouvez aller dans l'autre sens - désactiver instantanément puis, après un long délai, réactiver. Empêche les doubles clics, autorise la nouvelle soumission, mais autorise toujours les soumissions invalides pour les utilisateurs en retard
Ctrl-C
8

Veuillez consulter le plugin jquery-safeform .

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;
    }
});
Max Kamenkov
la source
4

... mais le formulaire ne contient aucune des données POST qu'il est censé inclure.

Correct. Les noms / valeurs des éléments de formulaire désactivés ne seront pas envoyés au serveur. Vous devez les définir comme des éléments en lecture seule .

De plus, les ancres ne peuvent pas être désactivées comme ça. Vous devrez soit supprimer leurs HREF (non recommandé), soit empêcher leur comportement par défaut (meilleure façon), 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>
karim79
la source
@Adam, oui, tous les gestionnaires de clics attachés se déclencheront. Je pensais que vous vouliez juste les empêcher d'être modifiés?
karim79
@ karim79 Peu m'importe si les gestionnaires de clics sont renvoyés, je veux juste m'assurer que le formulaire ne soit pas soumis à nouveau par l'utilisateur en cliquant sur un autre bouton d'envoi.
Adam
@Adam - non, cela n'arrivera pas. J'ai mon exemple, j'ai ajouté $('input[type=submit]').attr("disabled", "disabled");mais ma manière préférée est de mettre onclick="this.disabled = 'disabled'"en un clic de la soumission.
karim79
@ karim79 J'ai essayé cela, mais le bouton d'envoi sur lequel je clique n'est pas inclus dans le POST. J'ai besoin qu'il soit inclus dans le POST pour déterminer sur quel bouton on a cliqué
Adam
Je sais que vous venez de dissocier l'événement click pour le 'a' mais pourquoi ne pas utiliser simplement $ ('a'). Click (function (e) {e.preventDefault (); // ou return false;}); ?
Fabio
4

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
Mikhail Fiadosenka
la source
1
Pourquoi diable utiliseriez-vous une minuterie pour cette approche?
Bobort
4

Code de Nathan mais pour le plugin jQuery Validate

Si vous utilisez le plugin jQuery Validate, ils ont déjà implémenté le gestionnaire de soumission, et 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 le développer dans le } else {bloc pour désactiver les entrées et / ou soumettre le bouton.

À votre santé

Alph.Dev
la source
2

J'ai fini par utiliser les idées de cet article pour trouver 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;
  });   
};

En utilisant comme ça:

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

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

Cela peut probablement être un peu embelli car ce n'est pas si compliqué.

Jacklin
la source
2

Si vous utilisez AJAX pour publier un formulaire, set async: falsedevrait empêcher les soumissions supplémentaires avant que le formulaire ne s'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;
   }
});
Al Kari
la source
3
La définition de <code> asnyc: true </code> est une mauvaise approche car elle gèle le navigateur jusqu'à ce que la requête soit complète, ce qui bat l'essence d'AJAX
Edwin M
2

Modification un peu de la solution de Nathan pour Bootstrap 3. Cela définira un texte de chargement sur le bouton d'envoi. De plus, il expirera au bout de 30 secondes et permettra au formulaire d'être resoumis.

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);
};
AlexZ
la source
2

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();
});
izmir_LEE
la source
2

Utilisez un compteur simple lors de la soumission.

    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 monitor()fonction lors de l' envoi du formulaire.

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

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

Si vous n'avez aucune validation côté client, vous pouvez simplement utiliser la méthode jquery one () comme documentée ici.

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

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

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

Si vous effectuez une validation côté client comme je le faisais, c'est un peu plus délicat. L'exemple ci-dessus ne vous permet pas de soumettre à nouveau après l'échec de la validation. Essayez plutôt cette approche

$("#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);
   }
});
mattbloke
la source
1

Vous pouvez arrêter le deuxième envoi par ceci

$("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
    });
});
Mohammed Zayan
la source
0

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

la source
0

Dans mon cas, la soumission du formulaire contenait un code de validation, donc j'incrémente la réponse de Nathan Long, y compris un point de contrôle à la soumission.

$.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;
    };
bocapio
la source
0

Modifier le bouton d'envoi:

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

Avec bouton normal:

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

Ensuite, utilisez la fonction de clic:

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

Et n'oubliez pas de réactiver le bouton lorsque cela est nécessaire:

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

Je ne peux pas croire le bon vieux truc 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 s'affiche 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');
})
Tom McDonough
la source
Cela n'arrête pas les soumissions de formulaires lorsque l'utilisateur appuie sur la Entertouche.
réformé
0

Pourquoi pas seulement cela - cela soumettra le formulaire mais désactivera également le bouton d'envoi,

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

gène b.
la source
0

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 renvoyer 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
});
Mohamed.Abdo
la source
-1

J'ai résolu un problème très similaire en utilisant:

$("#my_form").submit(function(){
    $('input[type=submit]').click(function(event){
        event.preventDefault();
    });
});
Nolan
la source