Impedisci la doppia presentazione dei moduli in jQuery


170

Ho un modulo che richiede un po 'di tempo per l'elaborazione del server. Devo assicurarmi che l'utente aspetti e non tenti di reinviare il modulo facendo nuovamente clic sul pulsante. Ho provato a utilizzare il seguente codice jQuery:

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

Quando provo questo in Firefox tutto viene disabilitato ma il modulo non viene inviato con nessuno dei dati POST che dovrebbe includere. Non riesco a utilizzare jQuery per inviare il modulo perché ho bisogno che il pulsante sia inviato con il modulo in quanto vi sono più pulsanti di invio e stabilisco quale è stato utilizzato da quale valore è incluso nel POST. Ho bisogno che il modulo sia inviato come di solito e devo disabilitare tutto subito dopo che ciò accade.

Grazie!


Grazie a tutti per il vostro aiuto!
Adam,

Risposte:


315

Aggiornamento nel 2018 : ho appena ricevuto alcuni punti per questa vecchia risposta e volevo solo aggiungere che la soluzione migliore sarebbe quella di rendere l'operazione idempotente in modo che gli invii duplicati siano innocui.

Ad esempio, se il modulo crea un ordine, inserire un ID univoco nel modulo. La prima volta che il server vede una richiesta di creazione dell'ordine con quell'id, dovrebbe crearla e rispondere "successo". I successivi invii dovrebbero anche rispondere al "successo" (nel caso in cui il cliente non ottenga la prima risposta) ma non dovrebbero cambiare nulla.

I duplicati devono essere rilevati tramite un controllo di unicità nel database per prevenire le condizioni di gara.


Penso che il tuo problema sia questa linea:

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

Stai disabilitando TUTTI gli input, compresi, immagino, quelli i cui dati dovrebbero essere inviati dal modulo.

Per disattivare solo il pulsante di invio (s), si potrebbe fare questo:

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

Tuttavia, non credo che IE invierà il modulo se anche quei pulsanti sono disabilitati. Suggerirei un approccio diverso.

Un plugin jQuery per risolverlo

Abbiamo appena risolto questo problema con il seguente codice. Il trucco qui sta usando jQuery data()per contrassegnare il modulo come già inviato o meno. In questo modo, non dobbiamo pasticciare con i pulsanti di invio, il che fa impazzire 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;
};

Usalo in questo modo:

$('form').preventDoubleSubmission();

Se ci sono moduli AJAX che dovrebbero essere autorizzati a inviare più volte per caricamento della pagina, puoi dare loro una classe che indica quello, quindi escluderli dal tuo selettore in questo modo:

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

2
Solo un puntatore: non sarebbe meglio memorizzare nella cache $(this)un a var that = $(this)?
Stuart.Sklinar,

5
@ Stuart.Sklinar - buona idea. Ho usato $formperò - 'form' perché è più descrittivo, e il principale $perché secondo la mia convenzione, significa che è un oggetto jQuery.
Nathan Long,

21
Quando si utilizza jquery convalidare la convalida discreta meglio per verificare se il modulo è valido. if ($ (this) .valid)) {$ form.data ('submit', true);}
cck

1
@cck Fantastico, l'ho usato. Hai rimosso un'ulteriore chiusura che ho rimosso: if ($ (this) .valid) {$ form.data ('submit', true);
Brian MacKay,

1
@BrianMacKay se il modulo non è valido e si preme il pulsante di invio questo plug-in jquery contrassegna l'attributo inviato come vero ma la convalida discreta impedisce l'invio a causa di errori di convalida. Dopo aver corretto gli errori non è possibile inviare il modulo perché l'attributo inviato è vero!
cck

44

L'approccio alla tempistica è sbagliato: come fai a sapere quanto tempo interverrà sul browser del client?

Come farlo

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

Quando il modulo viene inviato disabiliterà tutti i pulsanti di invio all'interno.

Ricorda, in Firefox quando disabiliti un pulsante questo stato verrà ricordato quando torni nella storia. Per evitare che sia necessario abilitare i pulsanti al caricamento della pagina, ad esempio.


2
+1 a causa del suggerimento di Firefox, ma c'è qualche altra opzione oltre all'abilitazione dei pulsanti al caricamento della pagina?
Raphael Isidro,

1
Questo è il modo più pulito di andare. Stavo cercando di disabilitare il pulsante di invio con un gestore eventi click ma il mio approccio stava causando problemi con Chrome. Questa soluzione funziona magnificamente.
Anthony, al

1
Fai molta attenzione con questo approccio, se hai un valore sul pulsante di invio e ne hai bisogno, il modulo non lo invierà.
Fabien Ménager,

Secondo l'ultima documentazione di jQuery dovresti usare .prop invece di .attr. Riferimento: api.jquery.com/prop/#entry-longdesc-1 "Il metodo .prop () dovrebbe essere usato per impostare disabilitato e controllato al posto del metodo .attr ()."
J Plana

19

Penso che la risposta di Nathan Long sia la strada da percorrere. Per me, sto usando la convalida lato client, quindi ho appena aggiunto una condizione che il modulo sia valido.

EDIT : se questo non viene aggiunto, l'utente non sarà mai in grado di inviare il modulo se la convalida sul lato client rileva un errore.

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

9

event.timeStampnon funziona in Firefox. Restituire false non è standard, è necessario chiamare event.preventDefault(). E mentre ci siamo, usa sempre le parentesi graffe con un costrutto di controllo .

Per riassumere tutte le risposte precedenti, ecco un plugin che fa il lavoro e funziona su più browser.

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

Per affrontare Kern3l, il metodo di cronometraggio funziona per me semplicemente perché stiamo cercando di interrompere un doppio clic sul pulsante di invio. Se hai un tempo di risposta molto lungo a un invio, ti consiglio di sostituire il pulsante di invio o il modulo con uno spinner.

Il blocco completo degli invii successivi del modulo, come la maggior parte degli esempi precedenti, ha un effetto collaterale negativo: se si verifica un errore di rete e desiderano provare a inviare nuovamente, non sarebbero in grado di farlo e perderebbero le modifiche che fatto. Ciò renderebbe sicuramente un utente arrabbiato.


2
Hai ragione. Puoi fare il contrario: disabilita istantaneamente e poi, dopo un lungo timeout, riattiva. Impedisce doppi clic, consente di inviare nuovamente, ma consente comunque di inviare nuovamente invalidi non validi per gli utenti in ritardo.
Ctrl-C

8

Per favore, controlla il plugin jquery-safeform .

Esempio di utilizzo:

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

4

... ma il modulo non viene inviato con nessuno dei dati POST che dovrebbe includere.

Corretta. I nomi / i valori degli elementi del modulo disabilitati non verranno inviati al server. Dovresti impostarli come elementi di sola lettura .

Inoltre, le ancore non possono essere disabilitate in questo modo. Dovrai rimuovere i loro HREF (non consigliati) o impedire il loro comportamento predefinito (modo migliore), ad esempio:

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

@Adam, sì, tutti i gestori dei clic allegati verranno attivati. Pensavo volessi solo impedire loro di essere modificati?
karim79,

@ karim79 Non mi interessa se i gestori dei clic vengono attivati, voglio solo assicurarmi che il modulo non venga reinviato dall'utente facendo clic su un altro pulsante di invio.
Adamo,

@Adam - no, ciò non accadrà. Io il mio esempio, l'ho aggiunto, $('input[type=submit]').attr("disabled", "disabled");ma il mio modo preferito è quello di inserire onclick="this.disabled = 'disabled'"il onclick dell'invio.
karim79,

@ karim79 L'ho provato, ma il pulsante Invia che sto facendo clic non è incluso nel POST. Ho bisogno che sia incluso nel POST per determinare su quale pulsante è stato fatto clic
Adam

So che hai appena annullato l'evento click per 'a' ma perché non usare semplicemente $ ('a'). Click (function (e) {e.preventDefault (); // or return false;}); ?
Fabio,

4

C'è la possibilità di migliorare l' approccio di Nathan Long . È possibile sostituire la logica per il rilevamento del modulo già inviato con questo:

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

1
Perché mai dovresti usare un timer per questo approccio?
Bobort,

4

Il codice di Nathan ma per il plugin jQuery Validate

Se ti capita di usare il plugin jQuery Validate, hanno già implementato il gestore di invio e in quel caso non c'è motivo di implementarne più di uno. Il codice:

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

Puoi facilmente espanderlo all'interno del } else {blocco per disabilitare input e / o pulsante di invio.

Saluti


2

Ho finito per usare le idee di questo post per trovare una soluzione che è abbastanza simile alla versione di 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;
  });   
};

Usando così:

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

Ho scoperto che le soluzioni che non includevano un qualche tipo di timeout ma solo l'invio disabilitato o gli elementi del modulo disabilitati hanno causato problemi perché una volta attivato il blocco non è possibile inviare nuovamente fino a quando non si aggiorna la pagina. Ciò causa alcuni problemi per me quando faccio cose ajax.

Questo può probabilmente essere un po 'più bello perché non è così elegante.


2

Se si utilizza AJAX per inviare un modulo, set async: falsedovrebbe impedire ulteriori invii prima che il modulo venga cancellato:

$("#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;
   }
});

3
L'impostazione di <code> asnyc: true </code> è un approccio errato perché blocca il browser fino al completamento della richiesta, il che batte l'essenza di AJAX
Edwin M

2

Modificata la soluzione di Nathan un po 'per Bootstrap 3. Questo imposterà un testo di caricamento sul pulsante di invio. Inoltre, scadrà dopo 30 secondi e consentirà di inviare nuovamente il modulo.

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

Usa due pulsanti di invio.

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

E jQuery:

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

2

Usa un semplice contatore al momento dell'invio.

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

E chiama la monitor()funzione su invia il modulo.

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

1

Ho riscontrato problemi simili e le mie soluzioni sono le seguenti.

Se non disponi di alcuna convalida lato client, puoi semplicemente utilizzare il metodo jquery one () come documentato qui.

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

Questo disabilita il gestore dopo che è stato invocato.

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

Se stai eseguendo la convalida lato client come stavo facendo, è leggermente più complicato. L'esempio precedente non ti consente di inviare nuovamente dopo la convalida non riuscita. Prova invece questo approccio

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

È possibile interrompere il secondo invio da questo

$("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

La mia soluzione:

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

Nel mio caso, onsubmit del modulo aveva un codice di convalida, quindi ho incrementato la risposta di Nathan Long includendo un checkpoint di 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

Cambia pulsante di invio:

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

Con il pulsante normale:

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

Quindi utilizzare la funzione clic:

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

E ricorda il pulsante di riattivazione quando è necessario:

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

0

Non posso credere al buon vecchio trucco CSS degli eventi puntatore: nessuno non è stato ancora menzionato. Ho avuto lo stesso problema aggiungendo un attributo disabilitato ma questo non viene postato. Prova quanto segue e sostituisci #SubmitButton con l'ID del pulsante di invio.

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

Ciò non interrompe l'invio di moduli quando l'utente Enterpreme il tasto.
riformato il

0

Perché non solo questo: questo invierà il modulo ma disabiliterà anche il pulsante di invio,

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

Inoltre, se stai usando jQuery Validate, puoi mettere queste due righe sotto if ($('#myForm').valid()).


0

questo codice visualizzerà il caricamento sull'etichetta del pulsante e imposterà il pulsante su

disabilitare lo stato, quindi dopo l'elaborazione, riattivare e ripristinare il testo del pulsante originale **

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

-1

Ho risolto un problema molto simile usando:

$("#my_form").submit(function(){
    $('input[type=submit]').click(function(event){
        event.preventDefault();
    });
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.